Skip to content

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Oct 19, 2025

  • Load common.js and style.css sequentially for all config pages (no more "xy is undefined" due to missing common.js)
  • Restrict all requests in index.js to single connection

This greatly improves UI stability in low heap as well as bad wifi signal conditions and does not noticeably slow down any UI access.
I also increased the retries on failed requests from 1 to 5 but shortened the request interval so some requests may even be faster than before.

Combined with #4939 this makes the ESP8266 work flawlessly, I was even able to increase the number of LEDs from current 720 to 900 and its still more stable than it is currently. With this PR the UI keeps running down to about 6k of free heap whereas it is already struggeling at 10k without these changes.

the changes add ~900bytes to flash usage.

Summary by CodeRabbit

  • New Features

    • Dynamic resource loader for sequential CSS/JS loading with retry and a new loadResources API
    • WebSocket connector reuses an open socket and passes the socket to connection callbacks
  • Bug Fixes

    • Page hidden until resources finish loading to avoid flashes of unstyled content
    • Consolidated retry/error handling to reduce startup failures and improve init resilience
  • Chores

    • Startup refactored to async/Promise flows for more reliable initialization
    • Removed a redundant pre-flight memory check in WebSocket send logic

- load common.js and style.css sequentially for all config pages
- restrict all requrests in index.js to single connection
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Walkthrough

Adds a sequential JS/CSS resource loader and replaces static common.js/style.css includes with a retrying dynamic loader across many pages; refactors initialization/data loading in wled00/data/index.js to async/Promise flows; updates WebSocket reuse/handshake in wled00/data/common.js; removes an ESP8266 pre-check in wled00/ws.cpp.

Changes

Cohort / File(s) Summary
Resource loader & WebSocket
\wled00/data/common.js``
Adds loadResources(files, init) to sequentially load JS/CSS with retry, reveal the document after load, and invoke an init callback; changes connectWs(onOpen) to reuse top.window.ws when OPEN, call getLoc() before URL formation, pass the WebSocket into onOpen, and return the WebSocket.
Async init & data flow refactor
\wled00/data/index.js``
Converts many callback flows to async/Promise-based functions (loadPalettes, loadFX, loadFXData, loadPalettesData, requestJson, loadSkinCSS, populatePresets, etc.), adds retry handling, and centralizes init into a sequential async startup path with unified error catches.
Settings pages — dynamic loader + hide-until-ready
Settings HTMLs
\wled00/data/settings.htm`, `wled00/data/settings_2D.htm`, `wled00/data/settings_dmx.htm`, `wled00/data/settings_leds.htm`, `wled00/data/settings_sec.htm`, `wled00/data/settings_sync.htm`, `wled00/data/settings_time.htm`, `wled00/data/settings_ui.htm`, `wled00/data/settings_um.htm`, `wled00/data/settings_wifi.htm``
Replace static <script src="common.js"> and inline @import style usage with IIFE-based dynamic loaders that retry on error, call loadResources(['style.css'], S) (or equivalent) after loading, remove body onload="S()", and add CSS to hide the page until resources are ready.
Liveview pages — loader & WS callback update
\wled00/data/liveview.htm`, `wled00/data/liveviewws2D.htm``
Switch to dynamic loading of common.js, remove body onload, update WebSocket usage to connectWs(ws => ...), and consolidate 2D liveview startup into a single S() flow with message handling inside that flow.
WebSocket memory check change (ESP8266)
\wled00/ws.cpp``
Removes an early heap-availability pre-check in sendDataWs() (ESP8266) that compared payload length to a cached heap value; buffer allocation and subsequent allocation-failure handling remain.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Heterogeneous changes: dense async refactor in index.js, API/contract change in connectWs, and many repetitive HTML loader edits.
  • Files needing extra attention:
    • wled00/data/index.js: correctness of async sequencing, retry boundaries, and places expecting older callback signatures.
    • wled00/data/common.js: loadResources retry/backoff, CSS insertion order, and changed connectWs callback contract.
    • wled00/ws.cpp: ensure removing the pre-check on ESP8266 does not introduce runtime allocation failures or degrade error signals.

Possibly related PRs

Suggested reviewers

  • netmindz
  • blazoncek
  • willmmiles

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Use sequential loading and requests for all UI resources' accurately and clearly summarizes the main change: converting to sequential loading of UI resources and requests to improve stability.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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: 2

🧹 Nitpick comments (6)
wled00/data/common.js (1)

54-82: Bound retries + backoff; avoid perma-hidden UI and tight retry loop

Current logic retries indefinitely every 100ms on load error, which can keep the UI hidden forever if a resource is missing or path is wrong. Add a bounded retry with backoff and a safe skip to the next resource, plus a once-only load handler and fix a small comment typo.

Apply this diff:

-function loadResources(files, init) {
-	let i = 0;
+function loadResources(files, init, opts) {
+	let i = 0;
+	const retries  = opts?.retries ?? 5;
+	const baseWait = opts?.delay  ?? 100;   // ms
+	const backoff  = opts?.backoff ?? 1.5;  // multiplier
+	const attempts = Object.create(null);
 	const loadNext = () => {
 		if (i >= files.length) {
 			if (init) {
-				d.documentElement.style.visibility = 'visible'; // make page visible after all files are laoded if it was hidden (prevent ugly display)
-				d.readyState === 'complete' ? init() : window.addEventListener('load', init);
+				d.documentElement.style.visibility = 'visible'; // make page visible after all files are loaded if it was hidden (prevent ugly display)
+				if (d.readyState === 'complete') init();
+				else window.addEventListener('load', init, { once: true });
 			}
 			return;
 		}
 		const file = files[i++];
 		const isCSS = file.endsWith('.css');
 		const el = d.createElement(isCSS ? 'link' : 'script');
 		if (isCSS) {
 			el.rel = 'stylesheet';
 			el.href = file;
 		} else {
 			el.src = file;
 		}
 		el.onload = () => {	setTimeout(loadNext, 0);};
-		el.onerror = () => {
-			i--; // load this file again
-			setTimeout(loadNext, 100);
-		};
+		el.onerror = () => {
+			const n = (attempts[file] || 0) + 1;
+			attempts[file] = n;
+			if (n >= retries) {
+				console.warn(`loadResources: giving up on ${file} after ${n} attempts`);
+				// skip this file and continue with the rest
+				setTimeout(loadNext, 0);
+				return;
+			}
+			// retry same file with capped exponential backoff
+			const wait = Math.min(5000, Math.round(baseWait * Math.pow(backoff, n - 1)));
+			i--; // reattempt current file
+			setTimeout(loadNext, wait);
+		};
 		d.head.appendChild(el);
 	};
 	loadNext();
 }

Optionally, let callers pass {retries, delay, backoff} to tune behavior per page.

wled00/data/settings_ui.htm (1)

11-18: Add bounded retries and minimal fallback for common.js loader

Prevent an infinite retry loop and blank page if common.js can’t be fetched. Add a small counter, backoff, and a user-visible fallback.

-	(function loadFiles() {
+	(function loadFiles() {
+		let tries = 0;
 		const l = document.createElement('script');
 		l.src = 'common.js';
-		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-		l.onerror = () => setTimeout(loadFiles, 100);
+		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
+		l.onerror = () => {
+			if (++tries >= 5) {
+				console.warn('Failed to load common.js after 5 attempts');
+				d.documentElement.style.visibility = 'visible';
+				alert('Failed to load common.js. Please reload the page.');
+				return;
+			}
+			setTimeout(loadFiles, Math.min(5000, 100 * tries)); // simple backoff
+		};
 		document.head.appendChild(l);
 	})();
wled00/data/settings_time.htm (1)

11-18: Harden common.js retry loop

Same as other settings pages: add bounded retries with backoff and a visible fallback to avoid an endlessly hidden page if common.js fails.

-	(function loadFiles() {
+	(function loadFiles() {
+		let tries = 0;
 		const l = document.createElement('script');
 		l.src = 'common.js';
 		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-		l.onerror = () => setTimeout(loadFiles, 100);
+		l.onerror = () => {
+			if (++tries >= 5) {
+				console.warn('Failed to load common.js after 5 attempts');
+				d.documentElement.style.visibility = 'visible';
+				alert('Failed to load common.js. Please reload the page.');
+				return;
+			}
+			setTimeout(loadFiles, Math.min(5000, 100 * tries));
+		};
 		document.head.appendChild(l);
 	})();
wled00/data/settings_um.htm (1)

13-21: Bound retries with backoff for loader; add UX on failure

Mirror the improved loader pattern so this page doesn’t get stuck hidden if common.js can’t be fetched.

-	(function loadFiles() {
+	(function loadFiles() {
+		let tries = 0;
 		const l = document.createElement('script');
 		l.src = 'common.js';
 		l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-		l.onerror = () => setTimeout(loadFiles, 100);
+		l.onerror = () => {
+			if (++tries >= 5) {
+				console.warn('Failed to load common.js after 5 attempts');
+				d.documentElement.style.visibility = 'visible';
+				alert('Failed to load common.js. Please reload the page.');
+				return;
+			}
+			setTimeout(loadFiles, Math.min(5000, 100 * tries));
+		};
 		document.head.appendChild(l);
 	})();
wled00/data/settings_sec.htm (1)

9-16: Avoid infinite retry loop; add backoff and user feedback

Harden the loader to prevent an endlessly hidden UI if common.js fails.

-		(function loadFiles() {
+		(function loadFiles() {
+			let tries = 0;
 			const l = document.createElement('script');
 			l.src = 'common.js';
 			l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-			l.onerror = () => setTimeout(loadFiles, 100);
+			l.onerror = () => {
+				if (++tries >= 5) {
+					console.warn('Failed to load common.js after 5 attempts');
+					d.documentElement.style.visibility = 'visible';
+					alert('Failed to load common.js. Please reload the page.');
+					return;
+				}
+				setTimeout(loadFiles, Math.min(5000, 100 * tries));
+			};
 			document.head.appendChild(l);
 		})();
wled00/data/settings_sync.htm (1)

48-50: Redundant getURL function definition.

The getURL function is defined locally on lines 48-50, but it's already provided by common.js which is loaded dynamically before S() is called (line 13). Since getURL is only called from within S() (lines 45-46) after common.js has loaded, this local definition appears redundant.

Consider removing the local definition to avoid duplication:

-function getURL(path) {
-	return (loc ? locproto + "//" + locip : "") + path;
-}

Note: Verify that no other code paths call getURL before common.js loads.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca5debe and 8fc6cac.

📒 Files selected for processing (13)
  • wled00/data/common.js (1 hunks)
  • wled00/data/index.js (13 hunks)
  • wled00/data/settings.htm (1 hunks)
  • wled00/data/settings_2D.htm (2 hunks)
  • wled00/data/settings_dmx.htm (2 hunks)
  • wled00/data/settings_leds.htm (3 hunks)
  • wled00/data/settings_sec.htm (2 hunks)
  • wled00/data/settings_sync.htm (2 hunks)
  • wled00/data/settings_time.htm (2 hunks)
  • wled00/data/settings_ui.htm (2 hunks)
  • wled00/data/settings_um.htm (2 hunks)
  • wled00/data/settings_wifi.htm (2 hunks)
  • wled00/ws.cpp (0 hunks)
💤 Files with no reviewable changes (1)
  • wled00/ws.cpp
🧰 Additional context used
📓 Path-based instructions (3)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/common.js
  • wled00/data/settings_dmx.htm
  • wled00/data/settings_time.htm
  • wled00/data/settings_um.htm
  • wled00/data/settings_leds.htm
  • wled00/data/settings_sec.htm
  • wled00/data/settings_wifi.htm
  • wled00/data/settings_sync.htm
  • wled00/data/settings_2D.htm
  • wled00/data/settings_ui.htm
  • wled00/data/index.js
  • wled00/data/settings.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/common.js
  • wled00/data/settings_dmx.htm
  • wled00/data/settings_time.htm
  • wled00/data/settings_um.htm
  • wled00/data/settings_leds.htm
  • wled00/data/settings_sec.htm
  • wled00/data/settings_wifi.htm
  • wled00/data/settings_sync.htm
  • wled00/data/settings_2D.htm
  • wled00/data/settings_ui.htm
  • wled00/data/index.js
  • wled00/data/settings.htm
wled00/data/settings*.htm

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name settings pages as settings*.htm within the web UI

Files:

  • wled00/data/settings_dmx.htm
  • wled00/data/settings_time.htm
  • wled00/data/settings_um.htm
  • wled00/data/settings_leds.htm
  • wled00/data/settings_sec.htm
  • wled00/data/settings_wifi.htm
  • wled00/data/settings_sync.htm
  • wled00/data/settings_2D.htm
  • wled00/data/settings_ui.htm
  • wled00/data/settings.htm
🧠 Learnings (1)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/settings*.htm : Name settings pages as settings*.htm within the web UI

Applied to files:

  • wled00/data/settings.htm
🪛 Biome (2.1.2)
wled00/data/index.js

[error] 2815-2816: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
🔇 Additional comments (13)
wled00/data/settings_ui.htm (3)

7-7: LGTM: prevent FOUC while dependencies load

Hiding html until resources load avoids flicker; loadResources() makes it visible on completion.


221-221: LGTM: onload removal aligns with deferred init

Init via loader keeps ordering guarantees and avoids “xy is undefined” before common.js.


7-7: File already uses correct tab-based indentation per guidelines

The indentation in wled00/data/settings_ui.htm is already compliant with coding guidelines—all indented lines use tabs (^I), not spaces. Ensure npm run build is run after making any web UI modifications to regenerate embedded headers before firmware compilation.

wled00/data/settings_time.htm (2)

7-7: LGTM: no-FUOC guard matches loadResources reveal

Hiding html until resource load completes is appropriate here.


132-132: LGTM: init moved off body onload

Consistent with the new sequential loader flow.

wled00/data/settings_um.htm (2)

7-7: LGTM: initial hide to prevent unstyled flash

Pairs correctly with loadResources’ reveal.


284-284: LGTM: body onload removal aligns with deferred init

Keeps init ordering consistent.

wled00/data/settings_sec.htm (2)

7-7: LGTM: FOUC prevention matches common.js reveal

Consistent with the shared loader behavior.


42-42: LGTM: loader-based init replaces body onload

Keeps dependency order deterministic.

wled00/data/settings_dmx.htm (1)

10-16: Verify infinite retry behavior is intentional.

The loadFiles IIFE will retry loading common.js indefinitely every 100ms on failure, whereas other data loading functions in index.js limit retries to 5 attempts. Under persistent network failures or misconfigurations, this could result in an infinite loop of retry attempts.

Consider whether a retry limit (matching the 5-retry pattern used elsewhere) would be more appropriate:

-(function loadFiles() {
+(function loadFiles(attempt = 0) {
 	const l = document.createElement('script');
 	l.src = 'common.js';
 	l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
-	l.onerror = () => setTimeout(loadFiles, 100);
+	l.onerror = () => { if (attempt < 5) setTimeout(() => loadFiles(attempt + 1), 100); };
 	document.head.appendChild(l);
 })();

This pattern appears in all settings_*.htm files.

wled00/data/index.js (3)

279-294: Sequential initialization looks good, but verify error handling doesn't halt progression.

The sequential loading pattern correctly addresses the low-heap/503-error issue mentioned in the PR objectives. Each async function properly resolves even on error, allowing initialization to continue.

One minor suggestion: Consider logging initialization progress for debugging:

 (async ()=>{
 	try {
+		console.log('[WLED] Starting initialization...');
 		await loadPalettes();        // loads base palettes and builds #pallist (safe first)
 		await loadFXData();          // loads fx data
 		await loadFX();              // populates effect list
 		await loadPalettesData();    // fills palettesData[] for previews
 		await requestJson();         // fills lastinfo.cpalcount (safe now, pallist exists)
 		populatePalettes();          // repopulate with custom palettes now that cpalcount is known
 		await loadPresets();         // load presets last
 		if (cfg.comp.css) await loadSkinCSS('skinCss');
+		console.log('[WLED] Initialization complete');
 		if (!ws) makeWS();
 	} catch(e) {
 		showToast("Init failed: " + e, true);
+		console.error('[WLED] Initialization error:', e);
 	}
 })();

516-537: Well-structured Promise conversion with retry logic.

The conversion from callback-based to Promise-based loading is clean and maintains the retry logic (5 attempts with 100ms intervals). The recursive retry pattern correctly preserves the Promise chain.


1693-1768: requestJson Promise conversion maintains backward compatibility well.

The Promise-based refactoring preserves the WebSocket fallback logic and correctly handles the command parameter. The retry logic (up to 10 attempts for requestJson vs 5 for data loading) seems intentional for higher reliability of state changes.

Minor observation: The higher retry count (10) for requestJson compared to data loading (5) appears deliberate for ensuring command reliability.

Copy link
Collaborator

@blazoncek blazoncek left a comment

Choose a reason for hiding this comment

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

I welcome the idea though I think it can be improved by reducing the code.
I did find a few oddities, too.

}
const file = files[i++];
const isCSS = file.endsWith('.css');
const el = d.createElement(isCSS ? 'link' : 'script');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you choose to implement JS loading as that already have a function for that, loadJS().

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

my function loads JS and CSS, I did not understand the purpose of the already implemented one. maybe they can be combined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I took a look at it and the two functions do server quite different purposes, unifying them does not really make sense and will just make the code more complex, at least that is how it looks to me.

if (t.validity.valid && command.transition==null) {
var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn;
async function requestJson(command=null) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

requestJson() is used on button press as well as other places in the code. I hope this does not break such use.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you know how to test this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Except by clicking everything, IDK.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Oct 20, 2025

I did find a few oddities, too.

I did mostly generate this using a series of AI requests but must admit I do not understand all of it in detail. So very well possible some of the code can be streamlined or improved upon. I did test it extensively on all platforms though.

@netmindz
Copy link
Member

Interesting idea to tweak the behaviour of the browser to make it a little more friendly to the microcontroller.

If we had request queuing in the server this might be less of an issue, but until then this might be a good idea

@blazoncek
Copy link
Collaborator

tweak the behaviour of the browser

I did that with the release of 0.14. It did not go well. Perhaps this implementation is better but needs to be thoroughly tested on multiple devices, especially in captive portal mode.

Copy link
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: 0

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)

485-513: Address maintainer feedback: Remove callback parameter.

As noted by blazoncek in previous review comments, the callback parameter is no longer necessary with the Promise-based pattern. Additionally, the early return at line 495 doesn't return a Promise, which will cause issues for callers using await loadPresets().

Apply this fix:

-async function loadPresets(callback = null) {
-	// 1st boot (because there is a callback)
-	if (callback && pmt == pmtLS && pmt > 0) {
-		// we have a copy of the presets in local storage and don't need to fetch another one
-		populatePresets(true);
-		pmtLast = pmt;
-		callback();
-		return;
-	}
-	// afterwards
-	if (!callback && pmt == pmtLast) return;
+async function loadPresets() {
+	// Check if already loaded
+	if (pmt == pmtLS && pmt > 0) {
+		populatePresets(true);
+		pmtLast = pmt;
+		return;
+	}
+	if (pmt == pmtLast) return;

	return new Promise((resolve) => {
		fetch(getURL('/presets.json'), {method: 'get'})
		.then(res => res.status=="404" ? {"0":{}} : res.json())
		.then(json => {
			pJson = json;
			pmtLast = pmt;
			populatePresets();
			resolve();
		})
		.catch(() => {
			presetError(false);
			resolve();
		})
-		.finally(() => {
-			if (callback) setTimeout(callback, 99);
-		});
	});
}

Update call sites at lines 288 and 2540 to remove callback arguments.

Based on past review comments.

♻️ Duplicate comments (2)
wled00/data/index.js (2)

19-19: Duplicate: Global retry variable still causes race conditions.

The change from false to 0 is appropriate for numeric retry counting, but the underlying race condition flagged in previous reviews remains unresolved. While the sequential await pattern in lines 279-294 prevents some concurrent access, the setTimeout retry paths (lines 530, 552, 576, 845) can still fire concurrently and corrupt the shared retry counter.


2806-2831: Duplicate: Self-comparison bug and undefined lastinfo.vid.

The issues flagged in previous reviews remain unresolved:

  1. Line 2814: d.vid == d.vid self-comparison is always true
  2. As blazoncek confirmed, lastinfo.vid is undefined at this point in the loading sequence

The simplest fix per previous discussion is to remove the ineffective version check and rely on the try-catch for corrupt data handling:

	if (lsPalData) {
		try {
			var d = JSON.parse(lsPalData);
-			if (d && d.vid == d.vid) {
+			if (d && d.p) {
				palettesData = d.p;
				return resolve();
			}
		} catch (e) {}
	}

Based on past review comments and static analysis hints.

🧹 Nitpick comments (2)
wled00/data/index.js (2)

279-294: Sequential loading trades speed for reliability.

The conversion to sequential resource loading addresses the stated goal of preventing 503 errors in low-heap conditions, and the ordering is correct (e.g., populatePalettes() at line 287 correctly follows requestJson() at line 286 to ensure lastinfo.cpalcount is available).

However, this approach significantly increases page load time compared to parallel fetching. Consider whether some independent resources (e.g., loadFXData() and loadPalettes()) could be safely parallelized using Promise.all() while keeping dependent resources sequential.


2833-2851: Consider completing async/await conversion.

getPalettesData() still uses a callback parameter while most other loaders have been converted to pure Promise-based APIs. For consistency and to eliminate the callback/Promise mixing, consider converting this to:

-function getPalettesData(page, callback) {
+async function getPalettesData(page) {
	fetch(getURL(`/json/palx?page=${page}`), {method: 'get'})
	.then(res => res.ok ? res.json() : Promise.reject())
	.then(json => {
		retry = 0;
		palettesData = Object.assign({}, palettesData, json.p);
-		if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75);
-		else callback();
+		if (page < json.m) return new Promise(resolve => setTimeout(async () => { await getPalettesData(page + 1); resolve(); }, 75));
	})
	.catch((error)=>{
		if (retry<5) {
			retry++;
-			setTimeout(()=>{getPalettesData(page,callback);}, 100);
+			return new Promise(resolve => setTimeout(async () => { await getPalettesData(page); resolve(); }, 100));
		} else {
			showToast(error, true);
-			callback();
		}
	});
}

Then update the call at line 2822 to use await getPalettesData(0).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fc6cac and 31618c2.

📒 Files selected for processing (3)
  • wled00/data/common.js (1 hunks)
  • wled00/data/index.js (13 hunks)
  • wled00/ws.cpp (0 hunks)
💤 Files with no reviewable changes (1)
  • wled00/ws.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/data/common.js
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/index.js
🧠 Learnings (4)
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Manually validate the web UI after changes (load index, navigation, color controls, effects, settings) and check browser console for JS errors

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (1)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (104-104)
  • ws (159-159)
🪛 Biome (2.1.2)
wled00/data/index.js

[error] 2814-2815: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32dev_debug)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
🔇 Additional comments (3)
wled00/data/index.js (3)

203-214: LGTM: Promise-based CSS loading.

The conversion to a Promise-based pattern is well-implemented with early resolution if the CSS is already loaded and proper event handlers for success/failure cases.


1692-1767: Verify async conversion doesn't break interactive usage.

As noted by blazoncek, requestJson() is called on button presses and throughout the codebase. The async conversion maintains backward compatibility since non-awaited calls will still work, but verify that interactive features (buttons, sliders, color changes) still function correctly, especially any code paths that may have relied on synchronous behavior.

Additionally, requestJson() uses a retry limit of 10 (line 1758) while other loaders use 5. Consider standardizing the retry count across all functions for consistency.

Manual testing required: Load the index page, test all interactive controls (power button, brightness slider, color picker, effect selection, preset loading) and verify no degradation in responsiveness.


279-294: Reminder: Regenerate embedded headers after changes.

Per coding guidelines, after modifying web UI files under wled00/data, run npm run build to regenerate the embedded headers before any firmware build.

As per coding guidelines.

…ack in loadPresets()

also udpated from main and added sequential loading to liveview files
Copy link
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

♻️ Duplicate comments (2)
wled00/data/liveviewws2D.htm (1)

17-24: Verify unlimited retry behavior for common.js loading.

Same concern as in liveview.htm (lines 21-28): the dynamic loader retries indefinitely on error without a maximum retry count. Ensure this unbounded retry behavior is intentional for this critical bootstrap dependency.

wled00/data/index.js (1)

2810-2810: Critical bug: self-comparison makes version check ineffective.

Line 2810 contains a self-comparison d.vid == d.vid which is always true. Based on the past review discussion, lastinfo.vid is not available at this point in the loading sequence, so a version check against it is not feasible.

Based on the past review discussion, remove the ineffective version check:

	try {
		var d = JSON.parse(lsPalData);
-		if (d && d.vid == d.vid) {
+		if (d && d.p) {
			palettesData = d.p;
			return resolve();
		}
	} catch (e) {}

Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31618c2 and e0155d4.

📒 Files selected for processing (5)
  • wled00/data/common.js (2 hunks)
  • wled00/data/index.js (12 hunks)
  • wled00/data/liveview.htm (3 hunks)
  • wled00/data/liveviewws2D.htm (1 hunks)
  • wled00/data/settings.htm (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/data/settings.htm
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/liveviewws2D.htm
  • wled00/data/liveview.htm
  • wled00/data/common.js
  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/liveviewws2D.htm
  • wled00/data/liveview.htm
  • wled00/data/common.js
  • wled00/data/index.js
🧠 Learnings (6)
📚 Learning: 2025-10-11T10:49:13.498Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4956
File: wled00/data/edit.htm:295-295
Timestamp: 2025-10-11T10:49:13.498Z
Learning: In the WLED codebase (wled00/data/), the variable `d` is defined in common.js as `var d=document;` and serves as a shorthand alias for `document`. This is a valid convention used throughout WLED's JavaScript files, so references to `d.getElementById()`, `d.body`, `d.onclick`, etc. should not be flagged as undefined variable errors.

Applied to files:

  • wled00/data/liveviewws2D.htm
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Manually validate the web UI after changes (load index, navigation, color controls, effects, settings) and check browser console for JS errors

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (2)
wled00/data/common.js (1)
wled00/data/index.js (11)
  • i (609-609)
  • i (742-742)
  • i (917-917)
  • i (1449-1449)
  • i (2191-2191)
  • i (2198-2198)
  • i (2215-2215)
  • i (2798-2798)
  • d (1382-1382)
  • e (1477-1477)
  • url (1391-1391)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (107-107)
  • ws (153-153)
🪛 Biome (2.1.2)
wled00/data/index.js

[error] 2810-2811: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: wled_build / Build Environments (usermods)
🔇 Additional comments (9)
wled00/data/liveview.htm (3)

21-28: Verify unlimited retry behavior for common.js loading.

The dynamic loader retries indefinitely on error (line 26) without a maximum retry count. This differs from the bounded retry logic (retry count 1→5) used elsewhere in the PR (e.g., index.js lines 524-530, 546-552). While persistent retries may be acceptable for a critical bootstrap dependency like common.js, confirm this is intentional and won't cause infinite retry loops in failure scenarios (e.g., missing file, network issues).

Consider adding a retry limit or timeout to prevent infinite retries:

  // load common.js with retry on error
- (function common() {
+ (function common(retries = 0) {
    const l = document.createElement('script');
    l.src = 'common.js';
    l.onload = () => S();
-   l.onerror = () => setTimeout(common, 100);
+   l.onerror = () => {
+     if (retries < 10) setTimeout(() => common(retries + 1), 100);
+     else console.error('Failed to load common.js after 10 retries');
+   };
    document.head.appendChild(l);
  })();

66-85: LGTM! Startup flow is well-structured.

The deferred initialization pattern (calling S() after common.js loads) properly sequences resource loading. The WebSocket callback pattern on line 72 aligns with the updated connectWs signature in common.js (lines 152-168), and error handling for message parsing is in place.


88-88: LGTM! Startup flow correctly delegated to dynamic loader.

Removing the onload="S()" attribute prevents double initialization now that S() is invoked by the dynamic common.js loader (line 25). This is the correct approach for the new sequential loading pattern.

wled00/data/common.js (1)

79-82: Verify unlimited retry behavior for individual resource loads.

The error handler (line 79-82) retries failed resource loads indefinitely without a maximum retry count. While this ensures resources eventually load, it could cause infinite retry loops if a resource is permanently unavailable (e.g., deleted file, misconfiguration). Consider whether a bounded retry approach (as used in index.js lines 524-530, 839-845) would be more robust.

If bounded retries are preferred, apply this diff:

+	let retries = {};
	const loadNext = () => {
		if (i >= files.length) {
			if (init) {
				d.documentElement.style.visibility = 'visible';
				d.readyState === 'complete' ? init() : window.addEventListener('load', init);
			}
			return;
		}
		const file = files[i++];
+		retries[file] = (retries[file] || 0);
		const isCSS = file.endsWith('.css');
		const el = d.createElement(isCSS ? 'link' : 'script');
		if (isCSS) {
			el.rel = 'stylesheet';
			el.href = file;
			const st = d.head.querySelector('style');
			if (st) d.head.insertBefore(el, st);
			else d.head.appendChild(el);
		} else {
			el.src = file;
			d.head.appendChild(el);
		}
		el.onload = () => {	setTimeout(loadNext, 0);};
		el.onerror = () => {
-			i--; // load this file again
-			setTimeout(loadNext, 100);
+			if (retries[file] < 10) {
+				i--; // load this file again
+				retries[file]++;
+				setTimeout(loadNext, 100);
+			} else {
+				console.error('Failed to load', file, 'after 10 retries');
+				setTimeout(loadNext, 0); // continue with next file
+			}
		};
	};
	loadNext();
wled00/data/liveviewws2D.htm (1)

32-61: LGTM! Startup flow is correct, pending fix in common.js.

The S() startup function properly initializes the canvas and WebSocket connection. The callback pattern on line 37 is correct and will work once the critical bug in common.js line 164 is fixed (see comment on wled00/data/common.js).

wled00/data/index.js (4)

203-214: LGTM! Clean Promise-based CSS loading.

The conversion of loadSkinCSS to a Promise-based implementation is well done. The function properly resolves on successful load and rejects on error, enabling sequential loading via await.


279-294: LGTM! Sequential initialization properly implements PR objectives.

The async IIFE correctly sequences resource loading to prevent "xy is undefined" errors caused by missing dependencies. The sequential await pattern ensures palettes load before presets, FX data loads before effects populate, etc. Error handling via try/catch provides visibility into initialization failures.


1688-1762: LGTM! Async conversion maintains functionality with improved sequencing.

The conversion to Promise-based flow is well-structured. The function properly handles both WebSocket and HTTP fetch paths, with appropriate retry logic (up to 10 attempts vs. 5 for other loaders—this may be intentional given requestJson's criticality). The sequential loading of presets and WebSocket creation (lines 1740-1746) prevents race conditions.


2829-2847: LGTM! Palette pagination with retry logic is sound.

The recursive pagination in getPalettesData properly handles multi-page palette data with retry on failure. Although it uses the global retry variable, the sequential pagination (line 2835) and per-page retry logic (lines 2839-2845) prevent interference. The past review concern about retry counter races was marked as addressed, and the current implementation appears correct.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Nov 13, 2025

If we had request queuing in the server this might be less of an issue, but until then this might be a good idea

there is request queuing, but each queue eats up RAM and if heap is low, they do get dropped and throw a 503 so I dont think this can ever be resolved on the server side.

@happysmartlight
Copy link

Hi @DedeHai

I’ve tested it, and the issue #5077 has indeed been resolved!

Hopefully, this branch will be merged soon to improve performance.

happysmartlight added a commit to happysmartlight/mach-choi-choi that referenced this pull request Nov 13, 2025
- removed pmtLast
- loadPresets() always loads the presets
- on page load, presets are loaded from localStorage if controller was not rebooted
Copy link
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: 0

♻️ Duplicate comments (1)
wled00/data/index.js (1)

502-523: Global retry variable still causes potential race conditions.

Past reviews flagged that the global retry variable (line 19) is shared across loadPalettes, loadFX, loadFXData, and requestJson. Although these functions are awaited sequentially during initialization (lines 282-286), their retry paths use setTimeout which can cause concurrent execution if retries are active when the next function starts.

For example:

  • loadPalettes fails at T=0, schedules retry at T=100ms
  • Meanwhile, loadFX runs at T=10 and fails, increments the same retry counter
  • The retry counter state is now corrupted between the two functions

A previous review comment indicated this was "Addressed in commits 30fbf55 to 31618c2", but the current code still uses the global retry variable.

Consider making retry a parameter to each function or using a closure/local variable to avoid interference.

🧹 Nitpick comments (2)
wled00/data/index.js (2)

279-295: Sequential initialization looks solid; consider more specific error reporting.

The async IIFE correctly sequences all initialization steps to avoid concurrent requests. However, since most individual loaders always resolve (never reject), the catch block may not capture intermediate failures. Consider logging which step failed or allowing individual steps to reject for better diagnostics.

Additionally, the populatePresets(true) call on line 288 will recursively call loadPresets() if pJson is falsy (line 589), which means the else await loadPresets() on line 289 might become redundant in that case. This logic works but could be clearer.


2809-2827: Consider removing callback parameter in favor of Promise pattern.

As noted in a previous review comment, the callback parameter is no longer necessary since loadPalettesData (line 2782) now returns a Promise. Consider refactoring getPalettesData to return a Promise and use async/await for cleaner pagination logic.

This would also make the retry handling more consistent with other loader functions and eliminate the callback pattern mixing with Promise-based code.

Based on past review suggestions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86b9556 and 523f944.

📒 Files selected for processing (1)
  • wled00/data/index.js (15 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/index.js
🧠 Learnings (4)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (1)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (107-107)
  • ws (153-153)
🪛 Biome (2.1.2)
wled00/data/index.js

[error] 2790-2791: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32dev_debug)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
🔇 Additional comments (1)
wled00/data/index.js (1)

1678-1743: Async conversion is compatible with all interactive use cases.

Verification confirms that all interactive call sites—including button presses (lines 1756, 1769, 1779), toggles (selSegEx, selSeg, setRev, setRevY), and slider changes—follow a fire-and-forget pattern: they invoke requestJson(obj) and return immediately without awaiting or checking the result. This pattern is fully compatible with async/Promise behavior.

The existing codebase already uses Promise semantics in requestJson calls (line 286 with await, line 1736 with .then().catch()), confirming the function was already async. No call site depends on synchronous completion, so the conversion introduces no breaking changes.

Note: The global retry variable race condition with different limits (5 vs. 10) is pre-existing and not specific to this PR.

Copy link
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: 2

♻️ Duplicate comments (3)
wled00/data/index.js (3)

1676-1740: Note: Retry counter issue also affects requestJson.

The same retry counter reset issue documented in lines 501-521 affects this function. Line 1677 declares let retry = 0, but line 1734's recursive call requestJson(command) creates a new scope with retry reset to 0, preventing the retry limit from working (line 1732 checks retry<10).


2806-2824: Retry counter reset issue affects pagination logic.

Same retry issue as other loading functions. Additionally, the pagination recursion (line 2812) and retry recursion (line 2818) both call getPalettesData(page, callback), but the retry counter resets on each call, so retry failures during pagination would retry infinitely.


2780-2803: Potential race condition: lastinfo.vid may be undefined during early initialization.

The version check at line 2787 compares d.vid == lastinfo.vid, but as noted in past reviews, lastinfo.vid may not be available when loadPalettesData() is called early in the initialization sequence (line 284, before requestJson() at line 285 populates lastinfo).

Consider either:

  1. Removing the version check and relying solely on the try-catch to validate cached data structure
  2. Adding a null check: if (d && d.vid && lastinfo.vid && d.vid == lastinfo.vid)
  3. Moving palette data loading after requestJson() completes

Apply this defensive fix:

 	try {
 		var d = JSON.parse(lsPalData);
-		if (d && d.vid == lastinfo.vid) {
+		if (d && d.p && lastinfo && lastinfo.vid && d.vid == lastinfo.vid) {
 			palettesData = d.p;
 			return resolve();
 		}
 	} catch (e) {}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 523f944 and 6351ea0.

📒 Files selected for processing (1)
  • wled00/data/index.js (15 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/index.js
🧠 Learnings (3)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (1)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (107-107)
  • ws (153-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32dev_debug)
🔇 Additional comments (4)
wled00/data/index.js (4)

202-213: LGTM: Clean Promise-based CSS loader.

The conversion to Promise-based loading is well-structured with proper error handling via reject, early exit if the resource is already loaded, and appropriate use of onload/onerror handlers.


2927-2927: LGTM: Proper focus state check.

The blur event guard correctly uses d.activeElement and d.hasFocus() to prevent hiding the filter UI when focus remains within the component.


3174-3247: Consistent use of document alias.

The DOM manipulation throughout the simplifyUI() function correctly uses the d variable (defined at line 17 as document), consistent with the established codebase pattern.


278-294: No changes needed—populatePalettes() is synchronous.

The populatePalettes() function (lines 946–980) contains only synchronous operations: array manipulation, string building, and DOM manipulation via innerHTML and classList. It does not return a Promise or perform any async work, so awaiting it at line 286 is unnecessary. The code is correct as written.

Comment on lines 584 to 587
async function populatePresets(fromls)
{
if (fromls) pJson = JSON.parse(localStorage.getItem("wledP"));
if (!pJson) {setTimeout(loadPresets,250); return;}
if (!pJson) {await loadPresets(); return;}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 13, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verify recursive call behavior in populatePresets.

Line 587 calls await loadPresets() recursively if pJson is null, which then calls populatePresets() again (line 491). This could lead to an infinite loop if loadPresets() fails silently and returns without setting pJson.

Verify that loadPresets() always sets pJson to at least an empty object (line 490 shows it does via res.status=="404" ? {"0":{}} : res.json()), so the guard should work. However, the .catch() block (line 494) calls presetError(false) but still resolves, meaning pJson remains undefined and line 587 would loop infinitely.

Apply this fix to prevent infinite recursion:

 async function populatePresets(fromls)
 {
 	if (fromls) pJson = JSON.parse(localStorage.getItem("wledP"));
-	if (!pJson) {await loadPresets(); return;}
+	if (!pJson) {
+		await loadPresets();
+		if (!pJson) pJson = {}; // prevent infinite recursion if loadPresets fails
+		return;
+	}
🤖 Prompt for AI Agents
In wled00/data/index.js around lines 584-587, populatePresets currently does "if
(!pJson) {await loadPresets(); return;}" which can trigger infinite recursion if
loadPresets() fails to set pJson; change the flow so after awaiting
loadPresets() you re-read and validate pJson and bail out safely rather than
returning into a caller that will re-invoke populatePresets. Concretely: after
await loadPresets(), set pJson = JSON.parse(localStorage.getItem("wledP")) (or
default to an empty object) and if it is still falsy return without calling
populatePresets again (or throw an error) so no recursive loop can occur.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

does presetError() not result in a valid pJson? that is its purpose

Copy link
Contributor

Choose a reason for hiding this comment

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

@DedeHai, looking at the code, presetError() does not set pJson to a valid value. Its purpose is to display an error message in the UI, not to initialize the variable.

Here's what presetError() actually does (lines ~323-349):

function presetError(empty)
{
	var hasBackup = false; var bckstr = "";
	try {
		bckstr = localStorage.getItem("wledP");
		if (bckstr.length > 10) hasBackup = true;
	} catch (e) {}

	var cn = `<div class="pres c" ...>`;
	// ... builds error message HTML ...
	gId('pcont').innerHTML = cn;  // <-- Only modifies DOM
	if (hasBackup) gId('bck').value = bckstr;
}

It only manipulates the DOM to show an error message (and optionally a backup restore UI). It never assigns anything to pJson.

So when loadPresets() fails:

  1. The .catch() block calls presetError(false)
  2. pJson remains undefined
  3. The promise resolves anyway
  4. Back in populatePresets(), after await loadPresets(), pJson is still falsy

While the immediate return prevents stack overflow, CodeRabbit's concern is valid: if anything retries calling populatePresets() (e.g., user interaction or a retry mechanism), it will attempt to load presets again, fail again, and repeat the cycle without ever establishing a valid pJson state.

CodeRabbit's suggestion to add a fallback initialization (if (!pJson) pJson = {}) after the await would prevent that scenario and ensure pJson is always defined after populatePresets() completes.

Copy link
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: 0

♻️ Duplicate comments (2)
wled00/data/index.js (2)

578-581: Critical: Potential infinite recursion remains unfixed.

A past review (still in comments) identified that line 581 can cause infinite recursion if loadPresets() fails (non-404 error). The .catch() block at lines 494-496 calls presetError(false) and resolves without setting pJson, so repeated calls to populatePresets(fromls) will loop indefinitely.

Apply this fix to prevent infinite recursion:

 async function populatePresets(fromls)
 {
 	if (fromls) pJson = JSON.parse(localStorage.getItem("wledP"));
-	if (!pJson) {await loadPresets(); return;}
+	if (!pJson) {
+		await loadPresets();
+		if (!pJson) pJson = {}; // prevent infinite recursion
+		return;
+	}

2772-2796: Version check may fail if lastinfo not yet populated.

The past self-comparison bug (d.vid == d.vid) appears fixed to d.vid == lastinfo.vid at line 2779. However, per previous review discussion, lastinfo.vid may be undefined when loadPalettesData() executes. The initialization sequence (line 284) calls loadPalettesData() before requestJson(), which populates lastinfo.

Add defensive checks:

 		try {
 			var d = JSON.parse(lsPalData);
-			if (d && d.vid == lastinfo.vid) {
+			if (d && d.vid && lastinfo && lastinfo.vid && d.vid === lastinfo.vid) {
 				palettesData = d.p;
 				return resolve();
 			}
 		} catch (e) {}

Alternatively, consider removing the version check entirely (as suggested in past review) since timing makes it unreliable.

🧹 Nitpick comments (1)
wled00/data/index.js (1)

2798-2814: Consider modernizing pagination to Promise-based pattern.

The retry logic is correctly implemented with a parameter. However, the callback pattern (while functional) is inconsistent with the Promise-based conversions elsewhere in this file.

Consider converting to a Promise-based recursive pagination:

-function getPalettesData(page, callback, retry=0) {
+function getPalettesData(page, retry=0) {
+	return new Promise((resolve, reject) => {
 		fetch(getURL(`/json/palx?page=${page}`), {method: 'get'})
 		.then(res => res.ok ? res.json() : Promise.reject())
 		.then(json => {
 			palettesData = Object.assign({}, palettesData, json.p);
-			if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75);
-			else callback();
+			if (page < json.m) {
+				setTimeout(()=>{ getPalettesData(page + 1, retry).then(resolve).catch(reject); }, 75);
+			} else {
+				resolve();
+			}
 		})
 		.catch((error)=>{
 			if (retry<5) {
-				setTimeout(()=>{getPalettesData(page,callback,retry+1);}, 100);
+				setTimeout(()=>{getPalettesData(page,retry+1).then(resolve).catch(reject);}, 100);
 			} else {
 				showToast(error, true);
-				callback();
+				resolve(); // or reject(error)
 			}
 		});
+	});
 }

Then update the caller at line 2787:

-	getPalettesData(0, () => {
+	getPalettesData(0).then(() => {
 		localStorage.setItem("wledPalx", JSON.stringify({
 			p: palettesData,
 			vid: lastinfo.vid
 		}));
 		redrawPalPrev();
-		setTimeout(resolve, 99);
+		resolve();
 	});

Based on learnings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6351ea0 and 3a003db.

📒 Files selected for processing (1)
  • wled00/data/index.js (15 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/index.js
🧠 Learnings (6)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-05T15:41:15.810Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4982
File: wled00/data/matrixtool/omggif.js:32-35
Timestamp: 2025-10-05T15:41:15.810Z
Learning: The file `wled00/data/matrixtool/omggif.js` is an external third-party library from https://github.com/deanm/omggif by Dean McNamee and should not be reviewed for code issues as it's maintained externally.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-28T08:09:20.630Z
Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (1)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (107-107)
  • ws (153-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
🔇 Additional comments (11)
wled00/data/index.js (11)

202-213: LGTM: Clean Promise-based CSS loader.

The conversion to a Promise-based pattern is well-structured with proper early return, error handling, and DOM manipulation.


485-499: LGTM: Async loadPresets follows consistent pattern.

The Promise-based conversion correctly handles the 404 case with a default preset object and ensures resolution even on errors, maintaining UI stability.


501-519: LGTM: Retry logic correctly implemented with local parameter.

The conversion addresses previous race condition concerns by using a function parameter instead of a global variable. The retry limit of 5 with 100ms intervals aligns with PR objectives for improved reliability.


521-539: LGTM: Consistent retry implementation.

The async conversion and retry logic follow the same pattern as loadPalettes, maintaining consistency across loader functions.


541-561: LGTM: Retry pattern consistent.

The async conversion and retry logic are correctly implemented. The array manipulation (shift/unshift) appears intentional for effect data processing.


1670-1733: Verify intentional higher retry limit.

The async conversion and retry logic are correctly implemented. However, requestJson() uses a retry limit of 10 (line 1725) while other loaders use 5. Confirm this is intentional—higher retries may be warranted for critical state updates, but could also prolong failure recovery.


2506-2506: LGTM: Delayed preset reload after save.

The 750ms delay allows the server to persist the preset before reloading, and the fire-and-forget pattern is appropriate here since saveP() is not async.


452-452: LGTM: Async function call from onclick handler.

Calling the async loadPresets() from an onclick handler works correctly; the returned Promise is appropriately ignored for this user-initiated action.


2917-2917: LGTM: DOM manipulations use correct API.

All DOM manipulations correctly use the d alias (defined line 17 as var d = document) and follow standard patterns.

Also applies to: 3164-3177, 3225-3225, 3237-3237


27-27: The review comment is based on incorrect assumptions and should be disregarded.

The original concern about variable shadowing is a false positive. Investigation reveals that:

  • index.html loads only index.js and does NOT load common.js
  • common.js is loaded separately in other pages (settings, liveview, etc.), but not in index.html
  • The ws variable in common.js:153 is a local variable inside the connectWs() function, not a global declaration
  • The ws variable in index.js:27 is global only within the index.html context

Since the two files are never loaded in the same scope, there is no shadowing, conflict, or collision between these variables. They operate independently in their respective contexts.

Likely an incorrect or invalid review comment.


278-294: The sequential initialization timing is correct; no changes needed.

The original concern about pmt timing can be dismissed. Here's why:

requestJson() returns a Promise that doesn't resolve until after the .then() chain completes. Critically, parseInfo(json.info) is called synchronously within the chain (line 1714) and assigns pmt = i.fs.pmt (line 633) before resolve() is called (line 1721). This means await requestJson() on line 285 will not return until pmt is fully assigned, so the conditional check at line 287 (if(pmt == pmtLS)) operates on a properly populated value.

Edge cases where info is not in the response (WebSocket fast-path, json.success, or empty responses) simply leave pmt at its initialized value (1 from line 24), which is harmless.

@happysmartlight
Copy link

Hi @DedeHai

Did you make additional updates on your side?
What does it improve?
What was the reason or motivation behind these new changes?

@DedeHai
Copy link
Collaborator Author

DedeHai commented Nov 14, 2025

see individual commits.

@netmindz
Copy link
Member

What testing is required @DedeHai ? Could you add some notes, it seems like at least one person has already treated and confirmed the improvement, so what it still to be tested?

@blazoncek
Copy link
Collaborator

@netmindz from my own experience these kind of changes need thorough testing on various mobile devices (especially Android) in captive portal mode.
Unfortunately one can only have so many.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Nov 15, 2025

@netmindz I ran some basic tests, also in AP mode and fixed some issues afterwards but I still want to re-test it, also on ESP8266 and also on android, firefox, safari. after those are successful, we can dare to merge this for further testing.

edit:
as for tests required, I really don't know, the "dangerous" changes are exclusively in index.js and the one bug that stands (palette preview not loaded in AP mode) is already present before this PR so I did not further investigate it yet. Things to look out for would be errors on high-wifi traffic / low wifi signal which I do not know how to emulate. Other than that I just poke around the UI, adding segments, presets, palettes etc. and see if it all works.

Copy link
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 (2)
wled00/data/index.js (2)

485-499: Consider awaiting populatePresets for consistent completion.

Line 491 calls populatePresets() without await, even though it's now an async function. This creates a "fire and forget" call where the Promise resolution on line 492 happens before preset population completes.

If the caller of loadPresets() expects presets to be fully populated when the Promise resolves (e.g., in the initialization sequence), this could cause race conditions. Consider whether line 491 should be:

-		populatePresets();
-		resolve();
+		await populatePresets();
+		resolve();

or if the current behavior is intentional for performance reasons, add a comment explaining why.


2799-2815: Consider removing callback parameter per past review.

The function signature still includes a callback parameter, which blazoncek suggested removing in a past review comment. While the current implementation works correctly for pagination, consider refactoring to return a Promise and use async/await:

async function getPalettesData(page = 0, retry = 0) {
  try {
    const json = await fetch(getURL(`/json/palx?page=${page}`)).then(res => res.ok ? res.json() : Promise.reject());
    palettesData = Object.assign({}, palettesData, json.p);
    if (page < json.m) {
      await new Promise(resolve => setTimeout(resolve, 75));
      await getPalettesData(page + 1, retry);
    }
  } catch (error) {
    if (retry < 5) {
      await new Promise(resolve => setTimeout(resolve, 100));
      await getPalettesData(page, retry + 1);
    } else {
      showToast(error, true);
    }
  }
}

Then update the caller on line 2788 to await getPalettesData(0);.

Based on past review feedback from blazoncek.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a003db and 633d661.

📒 Files selected for processing (1)
  • wled00/data/index.js (16 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/index.js
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)
Learnt from: blazoncek
Repo: wled/WLED PR: 4932
File: wled00/data/cpal/cpal.htm:14-14
Timestamp: 2025-09-14T18:43:59.338Z
Learning: In WLED, static web pages like cpal.htm are served directly at the root URL level (e.g., "/cpal.htm") via handleStaticContent() from embedded page data, regardless of their physical source folder location in the repository. Relative script references like "common.js" resolve correctly from the served URL path, not the source file path.
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : When modifying web UI files, run `npm run build` to regenerate embedded headers before any firmware build
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/index.htm : Main web interface entry file is index.htm; ensure it remains present and functional
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/html_*.h : DO NOT edit generated embedded web header files (wled00/html_*.h)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-10T18:34:06.550Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-28T08:09:20.630Z
Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-05T15:41:15.810Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4982
File: wled00/data/matrixtool/omggif.js:32-35
Timestamp: 2025-10-05T15:41:15.810Z
Learning: The file `wled00/data/matrixtool/omggif.js` is an external third-party library from https://github.com/deanm/omggif by Dean McNamee and should not be reviewed for code issues as it's maintained externally.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (1)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (107-107)
  • ws (153-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32c3dev_qio)
🔇 Additional comments (8)
wled00/data/index.js (8)

278-294: Sequential initialization flow looks correct.

The sequential loading pattern (await each step) should significantly improve reliability on low-heap devices by avoiding parallel request overload. The error handling with a single catch block is appropriate for initialization failures.

However, line 287 calls populatePresets(true) without await, while line 288 does await loadPresets(). This is intentional since populatePresets with fromls=true reads from localStorage (synchronous), but the function signature is now async. Consider whether line 287 should also be awaited for consistency, or document why it's not needed.


501-561: LGTM - Retry logic correctly implemented.

The retry parameter pattern for loadPalettes, loadFX, and loadFXData correctly addresses the previous issues with global retry counters and reset-on-recursion. Each function:

  • Tracks its own retry count via parameter
  • Retries up to 5 times with 100ms delay
  • Shows error toast on final failure but still resolves to allow initialization to continue

This should significantly improve reliability on poor Wi-Fi connections.


1671-1734: Verify requestJson callers handle async change correctly.

The conversion of requestJson to async with retry logic looks correct, but note:

  1. Retry count difference: This function retries up to 10 times (line 1726), while other loaders retry only 5 times. This may be intentional since user-triggered commands are more important, but verify this is the desired behavior.

  2. Caller impact: This function is called from many UI interaction handlers (togglePower, setFX, setPalette, etc.). The call sites don't await the result, treating it as "fire and forget." This should be acceptable for most UI operations, but verify that critical operations (e.g., preset saves, segment changes) don't need to wait for completion or handle errors.

Based on past review concerns from blazoncek about breaking button press usage.


452-452: LGTM - Function call syntax corrected.

The onclick handler now correctly includes () to call loadPresets(). This ensures the function executes when the user clicks to retry loading presets after an error.


202-213: LGTM - loadSkinCSS Promise conversion.

The conversion to return a Promise is clean and correct:

  • Early return if CSS already loaded (line 204)
  • Proper onload/onerror handlers for Promise resolution (lines 209-210)
  • Integrates well with the sequential initialization flow

866-866: LGTM - Segment count guard clause.

The early return when segCount === 0 prevents unnecessary processing and potential errors from iterating over an empty segment list.


3165-3238: LGTM - Consistent document alias usage.

The uses of d.createElement (lines 3165, 3178, 3226) are consistent with the document alias defined on line 17. The code properly uses this alias throughout for DOM manipulation.


578-581: Review comment is incorrect.

The infinite recursion concern is unfounded. pJson is initialized to {} at line 21 (var pJson = {}, eJson = {}, lJson = {};), not undefined. Since an empty object {} is truthy in JavaScript, the condition if (!pJson) on line 581 evaluates to false and loadPresets() is never called. Even if loadPresets() fails and calls presetError(false), pJson remains as {} (truthy), preventing any retry loop on subsequent calls to populatePresets().

Likely an incorrect or invalid review comment.

Copy link
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: 0

🧹 Nitpick comments (2)
wled00/data/index.js (2)

485-499: Loose equality operator with mixed types.

Line 488 uses res.status=="404" comparing a number to a string. While JavaScript's type coercion makes this work, use strict equality for clarity:

-.then(res => res.status=="404" ? {"0":{}} : res.json())
+.then(res => res.status===404 ? {"0":{}} : res.json())

2773-2815: Palette data loading correctly implements pagination with retries.

The Promise-based loadPalettesData properly wraps the callback-based getPalettesData pagination. The version check at line 2780 now works correctly because requestJson() is called earlier in the init sequence (line 284), ensuring lastinfo.vid is populated.

The hybrid callback/Promise pattern in getPalettesData is acceptable given the pagination requirements, and error handling correctly allows partial data on failure (line 2812), maintaining UI functionality.

Minor: The 99ms delay at line 2794 seems arbitrary; consider removing if unnecessary or documenting why it's needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 633d661 and 75413ff.

📒 Files selected for processing (1)
  • wled00/data/index.js (16 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/index.js
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/index.js
🧠 Learnings (14)
📓 Common learnings
Learnt from: blazoncek
Repo: wled/WLED PR: 4932
File: wled00/data/cpal/cpal.htm:14-14
Timestamp: 2025-09-14T18:43:59.338Z
Learning: In WLED, static web pages like cpal.htm are served directly at the root URL level (e.g., "/cpal.htm") via handleStaticContent() from embedded page data, regardless of their physical source folder location in the repository. Relative script references like "common.js" resolve correctly from the served URL path, not the source file path.
📚 Learning: 2025-08-26T11:51:21.817Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
Repo: wled/WLED PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/** : For web UI changes, edit files only under wled00/data (not firmware or generated files)

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-12T17:29:43.826Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-10T18:34:06.550Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-08-28T08:09:20.630Z
Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-10-05T15:41:15.810Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4982
File: wled00/data/matrixtool/omggif.js:32-35
Timestamp: 2025-10-05T15:41:15.810Z
Learning: The file `wled00/data/matrixtool/omggif.js` is an external third-party library from https://github.com/deanm/omggif by Dean McNamee and should not be reviewed for code issues as it's maintained externally.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-11-14T13:37:11.994Z
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-04-30T05:41:03.633Z
Learnt from: blazoncek
Repo: wled/WLED PR: 4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-11-14T13:37:30.955Z
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-11-14T13:37:30.955Z
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-11-14T13:22:20.525Z
Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:22:20.525Z
Learning: In WLED's image_loader.cpp and similar contexts, when suggesting cleanup or de-allocation code in error handlers, first verify whether the underlying library or function already handles cleanup after failure. For example, `new` expressions that throw exceptions do not assign partial pointers and automatically clean up storage, making explicit dealloc() calls in catch blocks unnecessary. Only suggest "safety" cleanup when the library/function's documented semantics do not already provide graceful failure handling.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: In WLED, code paths that appear unreachable may actually be safety fallbacks for low-heap conditions. These fallbacks are intentional defensive programming measures to ensure UI functionality is preserved when system resources are constrained.

Applied to files:

  • wled00/data/index.js
📚 Learning: 2025-09-28T09:53:42.670Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: WLED's heap panic recovery in the main loop intentionally prioritizes UI functionality over complete system reinitialization. Strip reinitialization may be deliberately skipped during heap panic as a last-resort measure to free enough memory to maintain a working UI.

Applied to files:

  • wled00/data/index.js
🧬 Code graph analysis (1)
wled00/data/index.js (1)
wled00/data/common.js (2)
  • l (107-107)
  • ws (153-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
🔇 Additional comments (5)
wled00/data/index.js (5)

202-213: LGTM: Clean Promise-based resource loading.

The conversion of loadSkinCSS to return a Promise is well-implemented with proper early-exit for already-loaded resources and correct error handling.


278-294: Sequential initialization improves low-heap stability.

The new Promise-based sequential loading correctly enforces resource order to prevent concurrent requests that could exhaust heap. The flow is logical: base data (palettes, effects) → device state → preview data → presets → optional resources (CSS, WebSocket).

The fire-and-forget IIFE pattern is acceptable here since the UI remains functional during progressive loading, and any errors are caught and displayed via toast.


501-561: Retry logic correctly implemented with local counters.

Each loader (palettes, effects, effect data) now maintains its own retry counter as a parameter, eliminating race conditions from the previous global retry variable. The 5-retry limit with 100ms backoff is appropriate for transient network/heap issues, and graceful degradation (resolve on exhaustion) ensures initialization completes even if individual resources fail.


1671-1734: Async conversion maintains backward compatibility.

The conversion of requestJson to async is well-executed. The function correctly supports both fire-and-forget calls (existing button handlers) and awaited calls (initialization sequence). All existing call sites use it as a statement without capturing the return value, so returning a Promise doesn't break functionality.

The retry logic (max 10 retries vs 5 for loaders) and graceful degradation (resolves after exhaustion) are appropriate for the interactive command path.

Based on past review discussions with blazoncek.


278-294: Sequential loading architecture effectively addresses low-heap stability concerns.

The PR successfully converts the initialization flow from parallel to sequential with the following improvements:

  1. Eliminated race conditions: Local retry parameters prevent concurrent retry counters from interfering
  2. Predictable resource loading: Sequential await chain ensures dependencies are met (e.g., lastinfo.vid available before palette cache check)
  3. Graceful degradation: All loaders resolve on failure, allowing initialization to continue with partial functionality
  4. Backward compatibility: requestJson() works for both awaited (init) and fire-and-forget (button handlers) usage patterns
  5. Appropriate retry policies: 5 retries for non-critical loaders, 10 for interactive commands

The 100ms retry delay and increased retry counts align with the PR's goal of improving stability under poor Wi-Fi conditions without causing noticeable slowdown.

Also applies to: 1671-1734

@DedeHai
Copy link
Collaborator Author

DedeHai commented Nov 16, 2025

@netmindz I did test this and found a few oddities I was later not able to reproduce. the last commit seems to solve #5080 but needs confirmation.
the biggest issue I found was on android the peek was not working at all and all the config pages were just black. Debugging showed that in the config pages, the "hidden" style element was not removed, leaving the pages blank. I then tried a different android device and it just worked. A full page refresh then fixed it. So I suspect the issue was a cached "common.js" which was lacking the init function that removes the "hidden" tag. I then added a timout to automatically remove the tag but then removed again as it does not make any sense to do that: if common.js is not loaded properly, the init of the page will fail anyway because S() is not called.
Other than that I did not find any bugs related to this PR so I think its good to merge.

edit:
tested with ESP8266 on Chrome, Safari, Firefox and Android. Also tested in AP mode in Chrome and Android. Not tested on iPhone and Opera.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Nov 16, 2025

just found one small bug I am not able to fix: when saving any of the settings pages, the "xxx settings saved..." sometimes shows up with no CSS styling i.e. in white. But not always. Any ideas on why that is and how to fix it?

edit:
there is another issue: the ws-connection appears to be less stable. On a C3 with high FPS (>100) the connection breaks when using "peek" and clicking a button. Need to investigate.

@DedeHai DedeHai added connectivity Issue regarding protocols, WiFi connection or availability of interfaces and removed Awaiting testing labels Nov 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

connectivity Issue regarding protocols, WiFi connection or availability of interfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants