From 49b85e3f73643886c6b0c9091104e9eef2752d38 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Mon, 17 Nov 2025 09:52:18 +0100 Subject: [PATCH 01/62] Allow actions show-next-tab and previous to loop --- .../@node-red/editor-client/src/js/ui/common/tabs.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index d9dc4b289..544a8636c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -607,6 +607,9 @@ RED.tabs = (function() { while(previous.length > 0 && previous.hasClass("hide-tab")) { previous = previous.prev(); } + if (previous.length === 0) { + previous = ul.find("li.red-ui-tab:not(.hide-tab)").last(); + } return previous; } function findNextVisibleTab(li) { @@ -617,6 +620,9 @@ RED.tabs = (function() { while(next.length > 0 && next.hasClass("hide-tab")) { next = next.next(); } + if (next.length === 0) { + next = ul.find("li.red-ui-tab:not(.hide-tab)").first(); + } return next; } function showTab(id) { From 4b906734c9d735ff2b6ada36307a88cb3dfb31e8 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 6 Dec 2025 00:21:16 +0900 Subject: [PATCH 02/62] Fix debug tab to copy displayed value --- .../@node-red/editor-client/src/js/ui/utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index cc570cd13..94e06297a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -265,7 +265,13 @@ RED.utils = (function() { var copyPayload = $('').appendTo(copyTools).on("click", function(e) { e.preventDefault(); e.stopPropagation(); - RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue"); + var payloadToCopy; + if (typeof msg === "number") { + payloadToCopy = obj.find(".red-ui-debug-msg-type-number").first().text(); + } else { + payloadToCopy = msg; + } + RED.clipboard.copyText(payloadToCopy, copyPayload, "clipboard.copyMessageValue"); }) RED.popover.tooltip(copyPayload,RED._("node-red:debug.sidebar.copyPayload")); if (enablePinning && strippedKey !== undefined && strippedKey !== '') { From 2c7a15ecb2a2b5c09463be9fea88fc151a4b05dc Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Sun, 7 Dec 2025 12:04:59 +0100 Subject: [PATCH 03/62] Move the logic to the right place --- .../@node-red/editor-client/src/js/ui/common/tabs.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index 544a8636c..7ae05e770 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -443,12 +443,18 @@ RED.tabs = (function() { } function activatePreviousTab() { var previous = findPreviousVisibleTab(); + if (previous.length === 0) { + previous = ul.find("li.red-ui-tab:not(.hide-tab)").last(); + } if (previous.length > 0) { activateTab(previous.find("a")); } } function activateNextTab() { var next = findNextVisibleTab(); + if (next.length === 0) { + next = ul.find("li.red-ui-tab:not(.hide-tab)").first(); + } if (next.length > 0) { activateTab(next.find("a")); } @@ -607,9 +613,6 @@ RED.tabs = (function() { while(previous.length > 0 && previous.hasClass("hide-tab")) { previous = previous.prev(); } - if (previous.length === 0) { - previous = ul.find("li.red-ui-tab:not(.hide-tab)").last(); - } return previous; } function findNextVisibleTab(li) { @@ -620,9 +623,6 @@ RED.tabs = (function() { while(next.length > 0 && next.hasClass("hide-tab")) { next = next.next(); } - if (next.length === 0) { - next = ul.find("li.red-ui-tab:not(.hide-tab)").first(); - } return next; } function showTab(id) { From 636089b58636a8816ef0719daf302cbcc3f6ccbe Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 12 Dec 2025 22:42:46 +0000 Subject: [PATCH 04/62] Fix flushing when in variable delay mode to close #5381 --- .../node_modules/@node-red/nodes/core/function/89-delay.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index 17cbd2f4f..c44fa06c5 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -192,7 +192,8 @@ module.exports = function(RED) { } done(); }, delayvar, () => done()); - node.idList.push(id); + if (Object.keys(msg).length === 2 && msg.hasOwnProperty("flush")) { id.clear(); } + else { node.idList.push(id); } if (msg.hasOwnProperty("reset")) { clearDelayList(true); } if (msg.hasOwnProperty("flush")) { flushDelayList(msg.flush); done(); } if (delayvar >= 0) { From 2355709040f4918420cab65470d9b722029b687a Mon Sep 17 00:00:00 2001 From: Dennis-SEG Date: Tue, 23 Dec 2025 21:32:49 +0100 Subject: [PATCH 05/62] fix(http-request): prevent uncaught exceptions in async hooks This PR fixes several issues that can cause uncaught exceptions and crash Node-RED: 1. Fixed typo: `toLowercase()` -> `toLowerCase()` in getHeaderValue() 2. Added try-catch to beforeRequest hook 3. Added try-catch to beforeRedirect hook 4. Added try-catch to afterResponse hook (digest auth) 5. Added input validation to extractCookies() with array check 6. Added input validation to buildDigestHeader() for nonce/realm These changes ensure that malformed responses or invalid data from servers don't crash the entire Node-RED runtime. Fixes: Uncaught exceptions in HTTP request node --- .../nodes/core/network/21-httprequest.js | 128 ++++++++++++------ 1 file changed, 83 insertions(+), 45 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 33d9c3044..4afd97fa6 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -149,7 +149,7 @@ in your Node-RED user directory (${RED.settings.userDir}). * @return {any} value */ const getHeaderValue = (headersObject, name) => { - const asLowercase = name.toLowercase(); + const asLowercase = name.toLowerCase(); return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)]; } this.count = 0; @@ -256,34 +256,42 @@ in your Node-RED user directory (${RED.settings.userDir}). opts.hooks = { beforeRequest: [ options => { - // Whilst HTTP headers are meant to be case-insensitive, - // in the real world, there are servers that aren't so compliant. - // GOT will lower case all headers given a chance, so we need - // to restore the case of any headers the user has set. - Object.keys(options.headers).forEach(h => { - if (originalHeaderMap[h] && originalHeaderMap[h] !== h) { - options.headers[originalHeaderMap[h]] = options.headers[h]; - delete options.headers[h]; + try { + // Whilst HTTP headers are meant to be case-insensitive, + // in the real world, there are servers that aren't so compliant. + // GOT will lower case all headers given a chance, so we need + // to restore the case of any headers the user has set. + Object.keys(options.headers).forEach(h => { + if (originalHeaderMap[h] && originalHeaderMap[h] !== h) { + options.headers[originalHeaderMap[h]] = options.headers[h]; + delete options.headers[h]; + } + }) + if (node.insecureHTTPParser) { + // Setting the property under _unixOptions as pretty + // much the only hack available to get got to apply + // a core http option it doesn't think we should be + // allowed to set + options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true } } - }) - if (node.insecureHTTPParser) { - // Setting the property under _unixOptions as pretty - // much the only hack available to get got to apply - // a core http option it doesn't think we should be - // allowed to set - options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true } + } catch (err) { + node.warn("Error in beforeRequest hook: " + err.message); } } ], beforeRedirect: [ (options, response) => { - let redirectInfo = { - location: response.headers.location + try { + let redirectInfo = { + location: response.headers.location + } + if (response.headers.hasOwnProperty('set-cookie')) { + redirectInfo.cookies = extractCookies(response.headers['set-cookie']); + } + redirectList.push(redirectInfo) + } catch (err) { + node.warn("Error processing redirect: " + err.message); } - if (response.headers.hasOwnProperty('set-cookie')) { - redirectInfo.cookies = extractCookies(response.headers['set-cookie']); - } - redirectList.push(redirectInfo) } ] } @@ -422,25 +430,30 @@ in your Node-RED user directory (${RED.settings.userDir}). let digestCreds = this.credentials; let sentCreds = false; opts.hooks.afterResponse = [(response, retry) => { - if (response.statusCode === 401) { - if (sentCreds) { - return response + try { + if (response.statusCode === 401) { + if (sentCreds) { + return response + } + const requestUrl = new URL(response.request.requestUrl); + const options = { headers: {} } + const normalisedHeaders = {}; + Object.keys(response.headers).forEach(k => { + normalisedHeaders[k.toLowerCase()] = response.headers[k] + }) + if (normalisedHeaders['www-authenticate']) { + let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname + requestUrl.search, normalisedHeaders['www-authenticate']) + options.headers.Authorization = authHeader; + } + // response.request.options.merge(options) + sentCreds = true; + return retry(options); } - const requestUrl = new URL(response.request.requestUrl); - const options = { headers: {} } - const normalisedHeaders = {}; - Object.keys(response.headers).forEach(k => { - normalisedHeaders[k.toLowerCase()] = response.headers[k] - }) - if (normalisedHeaders['www-authenticate']) { - let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname + requestUrl.search, normalisedHeaders['www-authenticate']) - options.headers.Authorization = authHeader; - } - // response.request.options.merge(options) - sentCreds = true; - return retry(options); + return response + } catch (err) { + node.warn("Digest authentication failed: " + err.message); + return response; } - return response }]; } else if (this.authType === "bearer") { opts.headers.Authorization = `Bearer ${this.credentials.password||""}` @@ -720,13 +733,29 @@ in your Node-RED user directory (${RED.settings.userDir}). function extractCookies(setCookie) { var cookies = {}; + if (!Array.isArray(setCookie)) { + return cookies; + } setCookie.forEach(function(c) { - var parsedCookie = cookie.parse(c); - var eq_idx = c.indexOf('='); - var key = c.substr(0, eq_idx).trim() - parsedCookie.value = parsedCookie[key]; - delete parsedCookie[key]; - cookies[key] = parsedCookie; + try { + if (typeof c !== 'string') { + return; + } + var parsedCookie = cookie.parse(c); + var eq_idx = c.indexOf('='); + if (eq_idx === -1) { + return; + } + var key = c.substr(0, eq_idx).trim() + if (!key) { + return; + } + parsedCookie.value = parsedCookie[key]; + delete parsedCookie[key]; + cookies[key] = parsedCookie; + } catch (err) { + // Skip malformed cookies + } }); return cookies; } @@ -778,6 +807,9 @@ in your Node-RED user directory (${RED.settings.userDir}). } function buildDigestHeader(user, pass, method, path, authHeader) { + if (!authHeader || typeof authHeader !== 'string') { + throw new Error("Invalid or missing WWW-Authenticate header"); + } var challenge = {} var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi for (;;) { @@ -787,6 +819,12 @@ in your Node-RED user directory (${RED.settings.userDir}). } challenge[match[1]] = match[2] || match[3] } + if (!challenge.nonce) { + throw new Error("Invalid digest challenge: missing nonce"); + } + if (!challenge.realm) { + throw new Error("Invalid digest challenge: missing realm"); + } var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth' var nc = qop && '00000001' var cnonce = qop && uuid().replace(/-/g, '') From f1b56dc6fad129c71d8992405672614dd946b42b Mon Sep 17 00:00:00 2001 From: Dennis-SEG Date: Wed, 24 Dec 2025 00:46:07 +0100 Subject: [PATCH 06/62] refactor: remove unused getHeaderValue() function --- .../@node-red/nodes/core/network/21-httprequest.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 4afd97fa6..bd68b458c 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -143,15 +143,6 @@ in your Node-RED user directory (${RED.settings.userDir}). }); } } - /** - * @param {Object} headersObject - * @param {string} name - * @return {any} value - */ - const getHeaderValue = (headersObject, name) => { - const asLowercase = name.toLowerCase(); - return headersObject[Object.keys(headersObject).find(k => k.toLowerCase() === asLowercase)]; - } this.count = 0; this.on("input",function(msg,nodeSend,nodeDone) { node.count++; From 811aaa9df446a2cccf9a043858d8c22cc6e3ab4c Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 29 Dec 2025 22:25:57 +0900 Subject: [PATCH 08/62] Fix scrolling issue in Git config UI --- .../@node-red/editor-client/src/sass/projects.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index f6bd57375..b94504ba5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -830,7 +830,13 @@ div.red-ui-projects-dialog-ssh-public-key { } #red-ui-settings-tab-gitconfig { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; padding: 8px 20px 20px; + overflow-y: auto; } .red-ui-settings-section-description { color: var(--red-ui-secondary-text-color); From a2fdb198d61472c0d55fd9998ad4abbefb5609d6 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 29 Dec 2025 23:30:02 +0900 Subject: [PATCH 09/62] Fix list size in Git config UI --- .../node_modules/@node-red/editor-client/src/sass/projects.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index b94504ba5..176c430b0 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -755,6 +755,7 @@ div.red-ui-projects-dialog-ssh-public-key { } .red-ui-projects-dialog-list { + display: block; position: relative; .red-ui-editableList-container { padding: 1px; From a401ea9216b10a07e22659092eb99b7a8fb95403 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 30 Dec 2025 00:47:11 +0900 Subject: [PATCH 10/62] Stricter validator for flow file name in project feature --- .../@node-red/editor-client/src/js/ui/projects/projects.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index f32e14c33..a49b0dd08 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -720,7 +720,7 @@ RED.projects = (function() { var validateForm = function() { var valid = true; var flowFile = projectFlowFileInput.val(); - if (flowFile === "" || !/\.json$/.test(flowFile)) { + if (flowFile === "" || !/^[a-zA-Z0-9\-_]+\.json$/.test(flowFile)) { valid = false; if (!projectFlowFileInput.hasClass("input-error")) { projectFlowFileInput.addClass("input-error"); @@ -1142,7 +1142,7 @@ RED.projects = (function() { } else if (projectType === 'empty') { var flowFile = projectFlowFileInput.val(); - if (flowFile === "" || !/\.json$/.test(flowFile)) { + if (flowFile === "" || !/^[a-zA-Z0-9\-_]+\.json$/.test(flowFile)) { valid = false; if (!projectFlowFileInput.hasClass("input-error")) { projectFlowFileInput.addClass("input-error"); From 01ae461911f2a9e7c3f9b3415a1a493b7cf30402 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 30 Dec 2025 01:01:51 +0900 Subject: [PATCH 11/62] Expand folder to avoid error in library --- .../node_modules/@node-red/editor-client/src/js/ui/library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index 64b1f1547..a94bfb67c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -327,7 +327,7 @@ RED.library = (function() { icon: 'fa fa-cube', label: options.type, path: "", - expanded: false, + expanded: true, children: function(done, item) { loadLibraryFolder(lib.id, options.url, "", function(children) { item.children = children; From a55aa17939642adc6bbe17d123b56cb0aec54943 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 30 Dec 2025 10:43:59 -0800 Subject: [PATCH 12/62] Fix search dialog to preserve flow/subflow name casing & add unit tests --- .../editor-client/src/js/ui/search.js | 22 +- .../@node-red/editor-client/ui/search_spec.js | 190 ++++++++++++++++++ 2 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 test/unit/@node-red/editor-client/ui/search_spec.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 4ddab7419..cab4bf5d7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -49,9 +49,13 @@ RED.search = (function() { function indexNode(n) { var l = RED.utils.getNodeLabel(n); if (l) { - l = (""+l).toLowerCase(); - index[l] = index[l] || {}; - index[l][n.id] = {node:n,label:l} + const originalLabel = "" + l; + const indexLabel = originalLabel.toLowerCase(); + index[indexLabel] = index[indexLabel] || {}; + index[indexLabel][n.id] = { + node: n, + label: originalLabel + }; } l = l||n.label||n.name||n.id||""; @@ -683,7 +687,17 @@ RED.search = (function() { show: show, hide: hide, search: search, - getSearchOptions: getSearchOptions + getSearchOptions: getSearchOptions, + // Expose internals for testing + _indexNode: indexNode, + get _index() { return index; }, + set _index(val) { index = val; } }; })(); + + +// Allow CommonJS import for testing +if (typeof module !== "undefined" && module.exports) { + module.exports = RED.search; +} \ No newline at end of file diff --git a/test/unit/@node-red/editor-client/ui/search_spec.js b/test/unit/@node-red/editor-client/ui/search_spec.js new file mode 100644 index 000000000..ae63577ea --- /dev/null +++ b/test/unit/@node-red/editor-client/ui/search_spec.js @@ -0,0 +1,190 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +const should = require("should"); +const sinon = require("sinon"); + +const NR_TEST_UTILS = require("nr-test-utils"); + +// Path to the search.js module +const searchModulePath = NR_TEST_UTILS.resolve( + "@node-red/editor-client/src/js/ui/search.js" +); + +describe("editor-client/ui/search", function () { + let search; + let mockRED; + + beforeEach(function () { + // Set up minimal RED global mock - only what's needed for tests + mockRED = { + utils: { + getNodeLabel: sinon.stub(), + }, + }; + global.RED = mockRED; + + // Clear require cache to get fresh module instance + delete require.cache[searchModulePath]; + search = require(searchModulePath); + + // Reset the index for each test + search._index = {}; + }); + + afterEach(function () { + sinon.restore(); + delete global.RED; + delete require.cache[searchModulePath]; + }); + + describe("indexNode", function () { + it("preserves original label casing in search results", function () { + const node = { + id: "node1", + type: "tab", + _def: { category: "config" }, + }; + mockRED.utils.getNodeLabel.returns("MyFlow Name"); + + search._indexNode(node); + + // Verify the index key is lowercase (for case-insensitive searching) + should.exist(search._index["myflow name"]); + + // Verify the stored label preserves original casing + search._index["myflow name"]["node1"].label.should.equal("MyFlow Name"); + }); + + it("indexes node with mixed case label correctly", function () { + const node = { + id: "node2", + type: "subflow", + _def: { category: "subflows" }, + }; + mockRED.utils.getNodeLabel.returns("MySubFlow_Test"); + + search._indexNode(node); + + // Index key should be lowercase + should.exist(search._index["mysubflow_test"]); + + // Label should preserve original casing + search._index["mysubflow_test"]["node2"].label.should.equal( + "MySubFlow_Test" + ); + }); + + it("handles uppercase labels", function () { + const node = { + id: "node3", + type: "tab", + _def: { category: "config" }, + }; + mockRED.utils.getNodeLabel.returns("UPPERCASE FLOW"); + + search._indexNode(node); + + should.exist(search._index["uppercase flow"]); + search._index["uppercase flow"]["node3"].label.should.equal( + "UPPERCASE FLOW" + ); + }); + + it("handles lowercase labels", function () { + const node = { + id: "node4", + type: "tab", + _def: { category: "config" }, + }; + mockRED.utils.getNodeLabel.returns("lowercase flow"); + + search._indexNode(node); + + should.exist(search._index["lowercase flow"]); + search._index["lowercase flow"]["node4"].label.should.equal( + "lowercase flow" + ); + }); + + it("stores node reference correctly", function () { + const node = { + id: "node5", + type: "tab", + _def: { category: "config" }, + }; + mockRED.utils.getNodeLabel.returns("Test Flow"); + + search._indexNode(node); + + search._index["test flow"]["node5"].node.should.equal(node); + }); + + it("handles nodes without labels by falling back to id", function () { + const node = { + id: "node6", + type: "tab", + _def: { category: "config" }, + }; + mockRED.utils.getNodeLabel.returns(null); + + search._indexNode(node); + + // When there's no label from getNodeLabel, + // the node is still indexed by its id + should.exist(search._index["node6"]); + search._index["node6"]["node6"].label.should.equal("node6"); + }); + }); + + describe("search", function () { + it("finds nodes with case-insensitive search", function () { + // Manually set up index with mixed case labels + search._index = { + myflow: { + node1: { + node: { id: "node1", type: "tab", _def: { category: "config" } }, + label: "MyFlow", + }, + }, + }; + + // Search with lowercase should find the node + const results = search.search("myflow"); + results.length.should.equal(1); + results[0].label.should.equal("MyFlow"); + }); + + it("returns preserved casing in search results", function () { + search._index = { + "test subflow": { + node1: { + node: { + id: "node1", + type: "subflow", + _def: { category: "subflows" }, + }, + label: "Test SubFlow", + }, + }, + }; + + const results = search.search("test"); + results.length.should.equal(1); + results[0].label.should.equal("Test SubFlow"); + }); + }); +}); From 58c4fd585864bd224d30958bf80bf6fa49a54492 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 30 Dec 2025 10:48:23 -0800 Subject: [PATCH 13/62] Fix indenting with 4-spaces since there can be no arguments --- .../@node-red/editor-client/ui/search_spec.js | 292 +++++++++--------- 1 file changed, 140 insertions(+), 152 deletions(-) diff --git a/test/unit/@node-red/editor-client/ui/search_spec.js b/test/unit/@node-red/editor-client/ui/search_spec.js index ae63577ea..f23ada673 100644 --- a/test/unit/@node-red/editor-client/ui/search_spec.js +++ b/test/unit/@node-red/editor-client/ui/search_spec.js @@ -20,171 +20,159 @@ const sinon = require("sinon"); const NR_TEST_UTILS = require("nr-test-utils"); // Path to the search.js module -const searchModulePath = NR_TEST_UTILS.resolve( - "@node-red/editor-client/src/js/ui/search.js" -); +const searchModulePath = NR_TEST_UTILS.resolve("@node-red/editor-client/src/js/ui/search.js"); -describe("editor-client/ui/search", function () { - let search; - let mockRED; +describe("editor-client/ui/search", function() { + let search; + let mockRED; - beforeEach(function () { - // Set up minimal RED global mock - only what's needed for tests - mockRED = { - utils: { - getNodeLabel: sinon.stub(), - }, - }; - global.RED = mockRED; + beforeEach(function() { + // Set up minimal RED global mock - only what's needed for tests + mockRED = { + utils: { + getNodeLabel: sinon.stub() + } + }; + global.RED = mockRED; - // Clear require cache to get fresh module instance - delete require.cache[searchModulePath]; - search = require(searchModulePath); + // Clear require cache to get fresh module instance + delete require.cache[searchModulePath]; + search = require(searchModulePath); - // Reset the index for each test - search._index = {}; - }); - - afterEach(function () { - sinon.restore(); - delete global.RED; - delete require.cache[searchModulePath]; - }); - - describe("indexNode", function () { - it("preserves original label casing in search results", function () { - const node = { - id: "node1", - type: "tab", - _def: { category: "config" }, - }; - mockRED.utils.getNodeLabel.returns("MyFlow Name"); - - search._indexNode(node); - - // Verify the index key is lowercase (for case-insensitive searching) - should.exist(search._index["myflow name"]); - - // Verify the stored label preserves original casing - search._index["myflow name"]["node1"].label.should.equal("MyFlow Name"); + // Reset the index for each test + search._index = {}; }); - it("indexes node with mixed case label correctly", function () { - const node = { - id: "node2", - type: "subflow", - _def: { category: "subflows" }, - }; - mockRED.utils.getNodeLabel.returns("MySubFlow_Test"); - - search._indexNode(node); - - // Index key should be lowercase - should.exist(search._index["mysubflow_test"]); - - // Label should preserve original casing - search._index["mysubflow_test"]["node2"].label.should.equal( - "MySubFlow_Test" - ); + afterEach(function() { + sinon.restore(); + delete global.RED; + delete require.cache[searchModulePath]; }); - it("handles uppercase labels", function () { - const node = { - id: "node3", - type: "tab", - _def: { category: "config" }, - }; - mockRED.utils.getNodeLabel.returns("UPPERCASE FLOW"); + describe("indexNode", function() { + it("preserves original label casing in search results", function() { + const node = { + id: "node1", + type: "tab", + _def: { category: "config" } + }; + mockRED.utils.getNodeLabel.returns("MyFlow Name"); - search._indexNode(node); + search._indexNode(node); - should.exist(search._index["uppercase flow"]); - search._index["uppercase flow"]["node3"].label.should.equal( - "UPPERCASE FLOW" - ); + // Verify the index key is lowercase (for case-insensitive searching) + should.exist(search._index["myflow name"]); + + // Verify the stored label preserves original casing + search._index["myflow name"]["node1"].label.should.equal("MyFlow Name"); + }); + + it("indexes node with mixed case label correctly", function() { + const node = { + id: "node2", + type: "subflow", + _def: { category: "subflows" } + }; + mockRED.utils.getNodeLabel.returns("MySubFlow_Test"); + + search._indexNode(node); + + // Index key should be lowercase + should.exist(search._index["mysubflow_test"]); + + // Label should preserve original casing + search._index["mysubflow_test"]["node2"].label.should.equal("MySubFlow_Test"); + }); + + it("handles uppercase labels", function() { + const node = { + id: "node3", + type: "tab", + _def: { category: "config" } + }; + mockRED.utils.getNodeLabel.returns("UPPERCASE FLOW"); + + search._indexNode(node); + + should.exist(search._index["uppercase flow"]); + search._index["uppercase flow"]["node3"].label.should.equal("UPPERCASE FLOW"); + }); + + it("handles lowercase labels", function() { + const node = { + id: "node4", + type: "tab", + _def: { category: "config" } + }; + mockRED.utils.getNodeLabel.returns("lowercase flow"); + + search._indexNode(node); + + should.exist(search._index["lowercase flow"]); + search._index["lowercase flow"]["node4"].label.should.equal("lowercase flow"); + }); + + it("stores node reference correctly", function() { + const node = { + id: "node5", + type: "tab", + _def: { category: "config" } + }; + mockRED.utils.getNodeLabel.returns("Test Flow"); + + search._indexNode(node); + + search._index["test flow"]["node5"].node.should.equal(node); + }); + + it("handles nodes without labels by falling back to id", function() { + const node = { + id: "node6", + type: "tab", + _def: { category: "config" } + }; + mockRED.utils.getNodeLabel.returns(null); + + search._indexNode(node); + + // When there's no label from getNodeLabel, + // the node is still indexed by its id + should.exist(search._index["node6"]); + search._index["node6"]["node6"].label.should.equal("node6"); + }); }); - it("handles lowercase labels", function () { - const node = { - id: "node4", - type: "tab", - _def: { category: "config" }, - }; - mockRED.utils.getNodeLabel.returns("lowercase flow"); + describe("search", function() { + it("finds nodes with case-insensitive search", function() { + // Manually set up index with mixed case labels + search._index = { + "myflow": { + "node1": { + node: { id: "node1", type: "tab", _def: { category: "config" } }, + label: "MyFlow" + } + } + }; - search._indexNode(node); + // Search with lowercase should find the node + const results = search.search("myflow"); + results.length.should.equal(1); + results[0].label.should.equal("MyFlow"); + }); - should.exist(search._index["lowercase flow"]); - search._index["lowercase flow"]["node4"].label.should.equal( - "lowercase flow" - ); + it("returns preserved casing in search results", function() { + search._index = { + "test subflow": { + "node1": { + node: { id: "node1", type: "subflow", _def: { category: "subflows" } }, + label: "Test SubFlow" + } + } + }; + + const results = search.search("test"); + results.length.should.equal(1); + results[0].label.should.equal("Test SubFlow"); + }); }); - - it("stores node reference correctly", function () { - const node = { - id: "node5", - type: "tab", - _def: { category: "config" }, - }; - mockRED.utils.getNodeLabel.returns("Test Flow"); - - search._indexNode(node); - - search._index["test flow"]["node5"].node.should.equal(node); - }); - - it("handles nodes without labels by falling back to id", function () { - const node = { - id: "node6", - type: "tab", - _def: { category: "config" }, - }; - mockRED.utils.getNodeLabel.returns(null); - - search._indexNode(node); - - // When there's no label from getNodeLabel, - // the node is still indexed by its id - should.exist(search._index["node6"]); - search._index["node6"]["node6"].label.should.equal("node6"); - }); - }); - - describe("search", function () { - it("finds nodes with case-insensitive search", function () { - // Manually set up index with mixed case labels - search._index = { - myflow: { - node1: { - node: { id: "node1", type: "tab", _def: { category: "config" } }, - label: "MyFlow", - }, - }, - }; - - // Search with lowercase should find the node - const results = search.search("myflow"); - results.length.should.equal(1); - results[0].label.should.equal("MyFlow"); - }); - - it("returns preserved casing in search results", function () { - search._index = { - "test subflow": { - node1: { - node: { - id: "node1", - type: "subflow", - _def: { category: "subflows" }, - }, - label: "Test SubFlow", - }, - }, - }; - - const results = search.search("test"); - results.length.should.equal(1); - results[0].label.should.equal("Test SubFlow"); - }); - }); }); From 0689306569ebab9236417153f105da80346ac779 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 30 Dec 2025 10:55:54 -0800 Subject: [PATCH 14/62] Add new line at end of file --- .../node_modules/@node-red/editor-client/src/js/ui/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index cab4bf5d7..d57f05c3a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -700,4 +700,4 @@ RED.search = (function() { // Allow CommonJS import for testing if (typeof module !== "undefined" && module.exports) { module.exports = RED.search; -} \ No newline at end of file +} From 4ada84d3142b23f8a94586f68be2053240839572 Mon Sep 17 00:00:00 2001 From: Dennis-SEG Date: Wed, 31 Dec 2025 13:31:44 +0100 Subject: [PATCH 17/62] ci: enable tests on fix branches --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ee9c922ae..f2c93aa2f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,9 +2,10 @@ name: Run tests on: push: - branches: [ master, dev ] + branches: [ master, dev, 'fix/**' ] pull_request: branches: [ master, dev ] + workflow_dispatch: permissions: contents: read From 187ce3bea4bd431282383448d8390b2f80d21474 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 2 Jan 2026 14:37:30 +0900 Subject: [PATCH 18/62] Fix invalid node size in quick add dialog --- .../node_modules/@node-red/editor-client/src/sass/search.scss | 1 + .../@node-red/editor-client/src/sass/ui/common/editableList.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index 66ade8798..726c13714 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -80,6 +80,7 @@ width: 18px; height: 15px; margin-top: 1px; + flex-shrink: 0; } .red-ui-search-result-node-port { position: absolute; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss index 00b79b54a..4b8873fee 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss @@ -21,6 +21,7 @@ padding: 2px 16px 2px 4px; font-size: 0.9em; } + overflow-x: hidden; } .red-ui-editableList-container { padding: 5px; From 957f7010357c0cacd75459f89cddf3eebfe38d65 Mon Sep 17 00:00:00 2001 From: Piotr Bogun Date: Fri, 2 Jan 2026 15:19:04 -0600 Subject: [PATCH 19/62] Add focus class to new selected label after removing focus class from previously selected label --- .../@node-red/editor-client/src/js/ui/common/treeList.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js index 85c6f0992..957e0f663 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js @@ -879,6 +879,10 @@ } that._topList.find(".focus").removeClass("focus"); + + if (item.treeList.label) { + item.treeList.label.addClass("focus"); + } if (triggerEvent !== false) { this._trigger("select",null,item) From d8f9139d2e316d4ff4f46d7f6696e04269169079 Mon Sep 17 00:00:00 2001 From: Piotr Bogun Date: Fri, 2 Jan 2026 16:15:40 -0600 Subject: [PATCH 21/62] Add check for junction node type in the quick add menu --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index c459de1ff..111b5df2a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -6545,6 +6545,9 @@ RED.view = (function() { suggestedNodes = [suggestedNodes] } suggestedNodes = suggestedNodes.filter(n => { + if (n.type === 'junction') { + return true + } const def = RED.nodes.getType(n.type) if (def?.set && def.set.enabled === false) { // Exclude disabled node set From 8c594ac62753237530da75bbd62f82410254ecfe Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 3 Jan 2026 14:31:46 +0900 Subject: [PATCH 22/62] Decrement count of http requests after error --- .../node_modules/@node-red/nodes/core/network/21-httprequest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 33d9c3044..5194c7b01 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -688,6 +688,7 @@ in your Node-RED user directory (${RED.settings.userDir}). if (!sendErrorsToCatch) { nodeSend(msg); } + node.count--; nodeDone(); }); }); From bcbd364ddd97f61ff562b337fdd6f20a44cfe502 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 3 Jan 2026 17:04:01 +0900 Subject: [PATCH 23/62] Add tooltip to delete button --- .../@node-red/editor-client/src/js/ui/common/editableList.js | 2 ++ packages/node_modules/@node-red/nodes/core/network/05-tls.html | 3 +++ 2 files changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js index ab9c8a837..b5117aea0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js @@ -340,8 +340,10 @@ var deleteButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(li); $('',{class:"fa fa-remove"}).appendTo(deleteButton); li.addClass("red-ui-editableList-item-removable"); + var removeTip = RED.popover.tooltip(deleteButton, RED._("common.label.delete")); deleteButton.on("click", function(evt) { evt.preventDefault(); + removeTip.close(); var data = row.data('data'); li.addClass("red-ui-editableList-item-deleting") li.fadeOut(300, function() { diff --git a/packages/node_modules/@node-red/nodes/core/network/05-tls.html b/packages/node_modules/@node-red/nodes/core/network/05-tls.html index 8414e98ca..caadae6b5 100644 --- a/packages/node_modules/@node-red/nodes/core/network/05-tls.html +++ b/packages/node_modules/@node-red/nodes/core/network/05-tls.html @@ -167,12 +167,15 @@ $("#tls-config-button-cert-clear").on("click", function() { clearNameData("cert"); }); + RED.popover.tooltip($("#tls-config-button-cert-clear"), RED._("common.label.delete")); $("#tls-config-button-key-clear").on("click", function() { clearNameData("key"); }); + RED.popover.tooltip($("#tls-config-button-key-clear"), RED._("common.label.delete")); $("#tls-config-button-ca-clear").on("click", function() { clearNameData("ca"); }); + RED.popover.tooltip($("#tls-config-button-ca-clear"), RED._("common.label.delete")); if (RED.settings.tlsConfigDisableLocalFiles) { $("#node-config-row-uselocalfiles").hide(); From cc1662ab5cfb27a7bb056edcf26ae641788f8e8d Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 4 Jan 2026 16:17:16 +0900 Subject: [PATCH 24/62] Fix status node to retrieve status from all nodes --- .../@node-red/nodes/core/common/25-status.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/25-status.js b/packages/node_modules/@node-red/nodes/core/common/25-status.js index 8c56e2030..c494883b7 100644 --- a/packages/node_modules/@node-red/nodes/core/common/25-status.js +++ b/packages/node_modules/@node-red/nodes/core/common/25-status.js @@ -20,13 +20,15 @@ module.exports = function(RED) { function StatusNode(n) { RED.nodes.createNode(this,n); var node = this; - this.scope = n.scope || []; + this.scope = n.scope; // auto-filter out any directly connected nodes to avoid simple loopback - const w = this.wires.flat(); - for (let i=0; i < this.scope.length; i++) { - if (w.includes(this.scope[i])) { - this.scope.splice(i, 1); + if (Array.isArray(this.scope)) { + const w = this.wires.flat(); + for (let i = 0; i < this.scope.length; i++) { + if (w.includes(this.scope[i])) { + this.scope.splice(i, 1); + } } } From a05dbb0067d52cf0b88e0b6b80ceb7de4416cd13 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 4 Jan 2026 16:19:13 +0900 Subject: [PATCH 25/62] Fix typo in test case of status node --- test/nodes/core/common/25-status_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nodes/core/common/25-status_spec.js b/test/nodes/core/common/25-status_spec.js index 9457d4372..af5b9dab2 100644 --- a/test/nodes/core/common/25-status_spec.js +++ b/test/nodes/core/common/25-status_spec.js @@ -15,7 +15,7 @@ **/ var should = require("should"); -var catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-status.js"); +var statusNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-status.js"); var helper = require("node-red-node-test-helper"); describe('status Node', function() { @@ -27,7 +27,7 @@ describe('status Node', function() { it('should output a message when called', function(done) { var flow = [ { id:"n1", type:"status", name:"status", wires:[["n2"]], scope:[] }, {id:"n2", type:"helper"} ]; - helper.load(catchNode, flow, function() { + helper.load(statusNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n1.should.have.property('name', 'status'); From e108554ea043abd87e650623116cd0b53a95dd88 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 4 Jan 2026 17:36:18 +0900 Subject: [PATCH 26/62] Support source information in complete node --- packages/node_modules/@node-red/runtime/lib/flows/Flow.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index 02b976a32..f96a67780 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -699,6 +699,13 @@ class Flow { let toSend = msg; this.completeNodeMap[node.id].forEach((completeNode,index) => { toSend = redUtil.cloneMessage(msg); + toSend.complete = { + source: { + id: node.id, + type: node.type, + name: node.name + } + }; completeNode.receive(toSend); }) } From 4bc6d0a9bd1b133901c3f41a31991417374c1dd7 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 4 Jan 2026 19:17:28 +0900 Subject: [PATCH 27/62] Use TextDecoder() to decode UTF-8 characters --- .../node_modules/@node-red/editor-client/src/js/ui/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index cc570cd13..d060cec42 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -593,7 +593,7 @@ RED.utils = (function() { var sr = $('').appendTo(stringRow); var stringEncoding = ""; try { - stringEncoding = String.fromCharCode.apply(null, new Uint16Array(data)) + stringEncoding = new TextDecoder().decode(new Uint8Array(data)); } catch(err) { console.log(err); } From f4b8af9c29421eaf4bfeb17ac1285b58ca3ee38a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 5 Jan 2026 17:14:49 +0000 Subject: [PATCH 28/62] Update body-parser --- package.json | 2 +- packages/node_modules/@node-red/editor-api/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 666d5b0db..703369642 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "async-mutex": "0.5.0", "basic-auth": "2.0.1", "bcryptjs": "3.0.2", - "body-parser": "1.20.3", + "body-parser": "1.20.4", "chalk": "^4.1.2", "cheerio": "1.0.0-rc.10", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index d15b85f3e..fa42984c3 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -19,7 +19,7 @@ "@node-red/util": "4.1.2", "@node-red/editor-client": "4.1.2", "bcryptjs": "3.0.2", - "body-parser": "1.20.3", + "body-parser": "1.20.4", "clone": "2.1.2", "cors": "2.8.5", "express-session": "1.18.2", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index dd83cb393..e6913da41 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -18,7 +18,7 @@ "acorn": "8.15.0", "acorn-walk": "8.3.4", "ajv": "8.17.1", - "body-parser": "1.20.3", + "body-parser": "1.20.4", "cheerio": "1.0.0-rc.10", "content-type": "1.0.5", "cookie-parser": "1.4.7", From 7b62e06a49d2c007673b5db0cbd4788fe5bc8f1b Mon Sep 17 00:00:00 2001 From: Noley Holland <63617269+n-lark@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:55:42 -0800 Subject: [PATCH 29/62] Update test/unit/@node-red/editor-client/ui/search_spec.js to remove copyright Co-authored-by: Nick O'Leary --- .../@node-red/editor-client/ui/search_spec.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/unit/@node-red/editor-client/ui/search_spec.js b/test/unit/@node-red/editor-client/ui/search_spec.js index f23ada673..098810b3c 100644 --- a/test/unit/@node-red/editor-client/ui/search_spec.js +++ b/test/unit/@node-red/editor-client/ui/search_spec.js @@ -1,19 +1,3 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - const should = require("should"); const sinon = require("sinon"); From cdc83afdee5de0fde64746b40fcd2f4c99ebc13a Mon Sep 17 00:00:00 2001 From: Piotr Bogun Date: Mon, 5 Jan 2026 22:54:47 -0600 Subject: [PATCH 30/62] Call reveal on a treelist target when it gets focused --- .../@node-red/editor-client/src/js/ui/common/treeList.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js index 85c6f0992..e06fa1df4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js @@ -188,7 +188,10 @@ } else { that._topList.find(".focus").removeClass("focus") } - target.treeList.label.addClass('focus') + if (target.treeList.label) { + target.treeList.label.addClass('focus') + } + that.reveal(target); } }); this._data = []; From 906d63a368ea440ab614e818595c235af9efbcd9 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:23:42 +0100 Subject: [PATCH 31/62] Refactor README to use Markdown syntax with reference-style links - Convert badge links from HTML to Markdown [![]()]() syntax - Use reference-style links to keep header section clean and readable - Move all badge URLs to bottom of file for easier maintenance - Reduce header section from ~45 lines to ~27 lines --- README.md | 76 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index a8f03f3f5..30d39e3ca 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,30 @@ -

- License: Apache 2.0 - - Build Status - -
-
- - Node-RED Forum on Discourse - - - Node-RED GitHub Organisation Followers - - - Slack - -
-
+

+ +[![License][badge-license]][link-license] +[![Build Status][badge-build]][link-build] +

+[![Forum][badge-forum]][link-forum] +[![GitHub][badge-github]][link-github] +[![Slack][badge-slack]][link-slack] +

+

- +

+

Low-code programming for event-driven applications

-
+

- - Node-RED Library Nodes - - - Node-RED Library Flows - - - Node-RED Library Collections - + +[![Nodes][badge-nodes]][link-nodes] +[![Flows][badge-flows]][link-flows] +[![Collections][badge-collections]][link-collections] +

-
- - - -
+ +[![Node-RED Screenshot][screenshot]][link-nodered] ## Quick Start @@ -124,3 +110,25 @@ Node-RED is a project of the [OpenJS Foundation](http://openjsf.org). Copyright + + +[badge-license]: https://img.shields.io/badge/License-Apache_2.0-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec +[badge-build]: https://img.shields.io/github/actions/workflow/status/node-red/node-red/tests.yml?branch=master&label=Build%20Status&style=for-the-badge +[badge-forum]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscourse.nodered.org%2Fabout.json&query=%24.about.stats.users_count&suffix=%20members&label=Forum&logo=discourse&style=for-the-badge&color=0e76b2&logoColor=0e76b2&labelColor=ececec +[badge-github]: https://img.shields.io/badge/dynamic/json?url=https://api.github.com/orgs/node-red&query=$.followers&suffix=%20followers&label=GitHub%20org&style=for-the-badge&logo=github&logoColor=24292F&labelColor=ececec&color=24292F +[badge-slack]: https://img.shields.io/badge/Slack-Join%20Us-4A154B.svg?logo=slack&style=for-the-badge&logoColor=4A154B&labelColor=ececec +[badge-nodes]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dnode&query=$.meta.results.count&label=Nodes&style=for-the-badge&labelColor=ececec&color=8f0000 +[badge-flows]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dflow&query=$.meta.results.count&label=Flows&style=for-the-badge&labelColor=ececec&color=8f0000 +[badge-collections]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dcollection&query=$.meta.results.count&label=Collections&style=for-the-badge&labelColor=ececec&color=8f0000 +[screenshot]: https://nodered.org/images/node-red-screenshot.png + + +[link-license]: https://opensource.org/license/apache-2-0 +[link-build]: https://github.com/node-red/node-red/actions?query=branch%3Amaster +[link-forum]: https://discourse.nodered.org +[link-github]: https://github.com/node-red +[link-slack]: https://nodered.org/slack +[link-nodes]: https://flows.nodered.org/search?type=node +[link-flows]: https://flows.nodered.org/search?type=flow +[link-collections]: https://flows.nodered.org/search?type=collection +[link-nodered]: https://nodered.org/ From 0d45b39b6a898a37966d77a343c5b28800fc1474 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:33:50 +0100 Subject: [PATCH 32/62] Centering --- README.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 30d39e3ca..8a96428dc 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,20 @@ -

+

-[![License][badge-license]][link-license] -[![Build Status][badge-build]][link-build] +[![License][badge-license]][link-license] [![Build Status][badge-build]][link-build]

-[![Forum][badge-forum]][link-forum] -[![GitHub][badge-github]][link-github] -[![Slack][badge-slack]][link-slack] +[![Forum][badge-forum]][link-forum] [![GitHub][badge-github]][link-github] [![Slack][badge-slack]][link-slack]

-

+ -

- -

+### Low-code programming for event-driven applications -

Low-code programming for event-driven applications

- -

- -[![Nodes][badge-nodes]][link-nodes] -[![Flows][badge-flows]][link-flows] -[![Collections][badge-collections]][link-collections] - -

+[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] [![Node-RED Screenshot][screenshot]][link-nodered] +
+ ## Quick Start Check out https://nodered.org/docs/getting-started/ for full instructions on getting From 8f673208b013cae586a60943df85c0f1dbb5de23 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:35:17 +0100 Subject: [PATCH 33/62] Spacing --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8a96428dc..1e296302f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ ### Low-code programming for event-driven applications +
+ [![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] [![Node-RED Screenshot][screenshot]][link-nodered] From 287d3b36d571c23e3f5e73518e9e488205cf6c79 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:44:04 +0100 Subject: [PATCH 34/62] Improved layout --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1e296302f..1a45fd281 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@
+ + [![License][badge-license]][link-license] [![Build Status][badge-build]][link-build]

[![Forum][badge-forum]][link-forum] [![GitHub][badge-github]][link-github] [![Slack][badge-slack]][link-slack]

- +Node-RED Screenshot ### Low-code programming for event-driven applications @@ -13,8 +15,6 @@ [![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] -[![Node-RED Screenshot][screenshot]][link-nodered] -
## Quick Start @@ -111,7 +111,6 @@ Node-RED is a project of the [OpenJS Foundation](http://openjsf.org). Copyright [badge-nodes]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dnode&query=$.meta.results.count&label=Nodes&style=for-the-badge&labelColor=ececec&color=8f0000 [badge-flows]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dflow&query=$.meta.results.count&label=Flows&style=for-the-badge&labelColor=ececec&color=8f0000 [badge-collections]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fflows.nodered.org%2Fthings%3Fformat%3Djson%26per_page%3D1%26type%3Dcollection&query=$.meta.results.count&label=Collections&style=for-the-badge&labelColor=ececec&color=8f0000 -[screenshot]: https://nodered.org/images/node-red-screenshot.png [link-license]: https://opensource.org/license/apache-2-0 From b183b7f5569029909be8090bb2cdb63dfcd55267 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:45:15 +0100 Subject: [PATCH 35/62] Layout improvements --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1a45fd281..eb6dd2a9f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@
- +
+ +### Low-code programming for event-driven applications + +
[![License][badge-license]][link-license] [![Build Status][badge-build]][link-build]

@@ -9,10 +13,6 @@ Node-RED Screenshot -### Low-code programming for event-driven applications - -
- [![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections]
From caa50bc4f7be9b97beb9a57d17e766642ad43909 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:46:01 +0100 Subject: [PATCH 36/62] Layout improvements --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eb6dd2a9f..b3d30b9f1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@
+[![License][badge-license]][link-license] [![Build Status][badge-build]][link-build] +

+
### Low-code programming for event-driven applications
-[![License][badge-license]][link-license] [![Build Status][badge-build]][link-build] -

+ [![Forum][badge-forum]][link-forum] [![GitHub][badge-github]][link-github] [![Slack][badge-slack]][link-slack]

-Node-RED Screenshot +Node-RED Screenshot
[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] From 7ee047caddbe5f23dae50efcbfc054dfa9e71237 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:46:40 +0100 Subject: [PATCH 37/62] Layout improvements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3d30b9f1..f53f1560c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![License][badge-license]][link-license] [![Build Status][badge-build]][link-build]

-
+
### Low-code programming for event-driven applications From f217256f857082442e8fbe7869f3232530fcdc4b Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:47:41 +0100 Subject: [PATCH 38/62] Layout improvements --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f53f1560c..6794ff72d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@
+[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] + ### Low-code programming for event-driven applications
@@ -15,8 +17,6 @@ Node-RED Screenshot
-[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] -
## Quick Start From 752ea70ab0eb79f759e8c3938b5715e2ec460790 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:48:17 +0100 Subject: [PATCH 39/62] Layout improvements --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 6794ff72d..5f960ca4b 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,9 @@
+Node-RED Screenshot
[![Forum][badge-forum]][link-forum] [![GitHub][badge-github]][link-github] [![Slack][badge-slack]][link-slack] -

- -Node-RED Screenshot
From 22ec62808e8d0f151bbee39f3cd14e3ad20783a9 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:49:34 +0100 Subject: [PATCH 40/62] Optimisations --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5f960ca4b..d0795a598 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,14 @@
[![License][badge-license]][link-license] [![Build Status][badge-build]][link-build] -

-
+


[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] ### Low-code programming for event-driven applications -
- -Node-RED Screenshot
+
Node-RED Screenshot
[![Forum][badge-forum]][link-forum] [![GitHub][badge-github]][link-github] [![Slack][badge-slack]][link-slack] From a678e4b25013e5ee87494fadab3685aa5106db61 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 7 Jan 2026 10:50:14 +0100 Subject: [PATCH 41/62] Optimisation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0795a598..88aa78172 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License][badge-license]][link-license] [![Build Status][badge-build]][link-build] -


+

[![Nodes][badge-nodes]][link-nodes] [![Flows][badge-flows]][link-flows] [![Collections][badge-collections]][link-collections] From 26573c1b3ce122d7a64f1f4c1ac8c5fafaae729b Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 6 Jan 2026 10:44:14 +0000 Subject: [PATCH 42/62] Let TypedInput width be calculated - but forced --- .../@node-red/editor-client/src/sass/ui/common/typedInput.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index c4e3422fc..18708f2fa 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -20,6 +20,7 @@ border: 1px solid var(--red-ui-form-input-border-color); border-radius: 5px; height: 34px; + width: calc(100% - var(--typedInput-width-adjust)) !important; line-height: 14px; display: inline-flex; padding: 0; From 82a1f1217993633f41e42e0cd3d4bad00b952b0f Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 6 Jan 2026 14:06:39 +0000 Subject: [PATCH 43/62] Just fix file nodes for now --- .../editor-client/src/sass/ui/common/typedInput.scss | 2 +- .../node_modules/@node-red/nodes/core/storage/10-file.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index 18708f2fa..e22feb032 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -20,7 +20,7 @@ border: 1px solid var(--red-ui-form-input-border-color); border-radius: 5px; height: 34px; - width: calc(100% - var(--typedInput-width-adjust)) !important; + // width: calc(100% - var(--typedInput-width-adjust)); line-height: 14px; display: inline-flex; padding: 0; diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html index 57d028ead..8a7463188 100644 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html @@ -2,7 +2,7 @@