🎉 Node-RED 4.0.0-beta.2 released

Thanks Bart.

It was one of my dumb questions.
Ooops.

1 Like

Is there any breaking change in this version to be aware of?

The switch to baseline node.js v18 is the main breaking change it seems.

However, if you use JSONata in your own function nodes, that now needs to be treated as an async function it looks like.

1 Like

Thank you!

There is still something not right with the blue dots on subflow tabs. If you edit the subflow properties the dot appears, but it doesn't go away if you deploy.

Hi There,

I just installed 4.0.0.beta.2 and can I ask whether the new red line in the menu bar will remain?

new:

Screenshot 2024-04-14 at 15.52.45

old:

Screenshot 2024-04-14 at 15.52.57

The red line is - across the entire screen - very glaring and a little annoying on my eyes.

Somehow it also even more glaring with the sidebar is greyed out (e.g. when opening the settings):

Screenshot 2024-04-14 at 15.56.40

Is there a reason for the new red line? Does it fulfil a specific function or is it just decoration?

It was a change proposed here: Header Styling Improvements - Proposal (with Draft PR)

Didn't receive any objections at the time, but feedback is always welcome.

Thanks for the background details, I made my comments over there

I find that I have a short flow exported from this beta version that won't import into 3.1.9, giving

Error: TypeError: Cannot read properties of undefined (reading 'length')

I think it is the subflow that is causing the problem

[{"id":"ec0f63498106e425","type":"subflow","name":"Empty subflow property test","info":"","category":"","in":[],"out":[{"x":440,"y":80,"wires":[{"id":"48fce04ebb8a6bbe","port":0}]},{"x":440,"y":180,"wires":[{"id":"14b4297f97abdf9c","port":0}]}],"env":[{"name":"value1","type":"str","value":""},{"name":"value2","type":"str","value":""}],"meta":{},"color":"#DDAA99"},{"id":"48fce04ebb8a6bbe","type":"inject","z":"ec0f63498106e425","name":"","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"${value1}","payloadType":"str","x":210,"y":80,"wires":[[]]},{"id":"14b4297f97abdf9c","type":"inject","z":"ec0f63498106e425","name":"","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"${value2}","payloadType":"str","x":210,"y":180,"wires":[[]]},{"id":"9eca47c26aa9291e","type":"subflow:ec0f63498106e425","z":"bdd7be38.d3b55","name":"empty property test","env":[{"name":"value2","value":"a value","type":"str"}],"x":450,"y":3280,"wires":[["ad46caabb2f31ef4"],["9dc2dc5cebac6292"]]},{"id":"ad46caabb2f31ef4","type":"debug","z":"bdd7be38.d3b55","name":"VALUE 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":700,"y":3260,"wires":[]},{"id":"9dc2dc5cebac6292","type":"debug","z":"bdd7be38.d3b55","name":"VALUE 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":700,"y":3320,"wires":[]}]

Just had restart my beta.2 installed (running on my Linux Mint desktop) after installing @Paul-Reed latest simpletime module update and I'm faced with this

===================

20 Apr 11:14:20 - [info] Node-RED version: v4.0.0-beta.2
20 Apr 11:14:20 - [info] Node.js  version: v20.12.2
20 Apr 11:14:20 - [info] Linux 6.5.0-1016-oem x64 LE
20 Apr 11:14:20 - [info] Loading palette nodes
20 Apr 11:14:20 - [debug] logger Node logging turning on logging next 111 log points
20 Apr 11:14:20 - [info] logger Node Copyright 2020 Jaroslav Peter Prib
20 Apr 11:14:20 - [debug] logger Node test active
20 Apr 11:14:21 - [info] Settings file  : /home/simon/.node-red/settings.js
20 Apr 11:14:21 - [info] Context store  : 'default' [module=localfilesystem]
20 Apr 11:14:21 - [info] User directory : /home/simon/.node-red
20 Apr 11:14:21 - [warn] Projects disabled : editorTheme.projects.enabled=false
20 Apr 11:14:21 - [info] Flows file     : /home/simon/.node-red/flows.json
20 Apr 11:14:21 - [info] Server now running at http://127.0.0.1:1880/
20 Apr 11:14:21 - [warn] 

---------------------------------------------------------------------
Your flow credentials file is encrypted using a system-generated key.

If the system-generated key is lost for any reason, your credentials
file will not be recoverable, you will have to delete it and re-enter
your credentials.

You should set your own key using the 'credentialSecret' option in
your settings file. Node-RED will then re-encrypt your credentials
file using your chosen key the next time you deploy a change.
---------------------------------------------------------------------

20 Apr 11:14:21 - [info] Starting flows
20 Apr 11:14:21 - [info] Started flows
20 Apr 11:14:21 - [info] [tcp in:d7c0249d3f58dd78] listening on port 2883
20 Apr 11:14:21 - [info] [udp in:b429a00a6c19aa83] udp listener at 0.0.0.0:39988
20 Apr 11:14:21 - [info] [udp out:Playroom .30] udp re-use socket: 39988 -> 192.168.0.30:38899
20 Apr 11:14:21 - [info] [udp out:Sky switch .34] udp re-use socket: 39988 -> 192.168.0.34:38899
20 Apr 11:14:21 - [info] [udp out:Garden .40] udp re-use socket: 39988 -> 192.168.0.40:38899
20 Apr 11:14:21 - [warn] [alexa-remote-account:Win11Alexa] ENOENT: no such file or directory, open 'd:\alexa180923c.txt'
20 Apr 11:14:21 - [info] [alexa-remote-account:Win11Alexa] intialising "Win11Alexa" with the PROXY method and NO saved data...
20 Apr 11:14:21 - [warn] [alexa-remote-account:Win11Alexa] open localhost:3456 in your browser
20 Apr 11:14:21 - [info] [mqtt-broker:192_168_0_23] Connected to broker: mqtt://192.168.0.23:1883
20 Apr 11:14:21 - [info] [mqtt-broker:bd0b59ec09738e10] Connected to broker: mqtt://test.mosquitto.org:1883
20 Apr 11:14:21 - [red] Uncaught Exception:
20 Apr 11:14:21 - [error] TypeError: Cannot read properties of undefined (reading 'close')
    at FSWatcher.close (node:internal/fs/recursive_watch:86:31)
    at Timeout._onTimeout (/usr/lib/node_modules/node-red/node_modules/node-watch/lib/has-native-recursive.js:99:13)
    at listOnTimeout (node:internal/timers:573:17)
    at process.processTimers (node:internal/timers:514:7)
simon@simon-HP-Desktop-M01-F1xxx:~/.node-red$ 

I have a tab that uses the filemon node so after running in safe mode - I disabled it and then everything was fine again

But as soon as I re-enabled the tab and deployed - NR stopped again

Went back and just disabled the filemon node and no error

@cymplecy looks like an issue with the filemon node rather than the beta.

1 Like

Possibly an issue accessing the filesystem ?

Ignore that - that was just an unused Alexa node now removed anyway
(It was when this machine was running Windows)
Its the watch node that seems to be the issue

UI-dashboard elements within new subflow-function: using multiple ui-groups and subflow-sharing with values does not work

1. using multiple ui-groups does not work

I have created a subflow with control elements on the left-hand side (group_left) and the video with instructions on the right-hand side (group_right). However, when instantiating the subflow, only the first ui-group is used. If you look on the subflow preview, you will see that for both, group_left and group_right , env-group-left is used. Accordingly, there are not two groups but only one.

2. subflow-sharing with values does not work

If you import the following flow, it does not contain the UI information for the subflow instances.

[{"id":"2dc76c16782a0f28","type":"subflow","name":"Control | Video","info":"","category":"Learning Templates","in":[{"x":40,"y":100,"wires":[{"id":"4ae3f2307fe76502"}]}],"out":[{"x":540,"y":100,"wires":[{"id":"b2fa1dab31f28faf","port":0}]},{"x":540,"y":140,"wires":[{"id":"c41fc291958f40f6","port":0}]}],"env":[{"name":"tab","type":"ui_tab","value":"","ui":{"type":"conf-types"}},{"name":"group_left","type":"ui_group","value":"","ui":{"type":"conf-types"}},{"name":"group_right","type":"ui_group","value":"","ui":{"type":"conf-types"}},{"name":"youtube-iframe-url","type":"str","value":"","ui":{"type":"input","opts":{"types":["str"]}}},{"name":"instruction","type":"str","value":"Instruction"}],"meta":{},"color":"#3FADB5","icon":"font-awesome/fa-file-video-o"},{"id":"4ae3f2307fe76502","type":"function","z":"2dc76c16782a0f28","name":"Subflow Tabcontrol","func":"node.warn(env.get(\"NR_SUBFLOW_NAME\"));\nmsg.payload = { \"tab\": env.get(\"NR_SUBFLOW_NAME\").split(\":\")[0] };\nnode.warn(msg.payload);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":170,"y":100,"wires":[["42d44f55191bdca5"]]},{"id":"42d44f55191bdca5","type":"ui_ui_control","z":"2dc76c16782a0f28","name":"","events":"all","x":295,"y":100,"wires":[[]],"l":false},{"id":"b2fa1dab31f28faf","type":"ui_button","z":"2dc76c16782a0f28","name":"","group":"${group_left}","order":0,"width":0,"height":0,"passthru":false,"label":"Next","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":390,"y":100,"wires":[[]]},{"id":"c41fc291958f40f6","type":"ui_button","z":"2dc76c16782a0f28","name":"Back","group":"${group_left}","order":0,"width":0,"height":0,"passthru":false,"label":"Back","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":390,"y":140,"wires":[[]]},{"id":"27c35dc690efc5f0","type":"ui_button","z":"2dc76c16782a0f28","name":"","group":"${group_left}","order":0,"width":0,"height":0,"passthru":false,"label":"Repeat","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"repeat","topicType":"str","x":400,"y":180,"wires":[["e5b9f28d0bbb932a"]]},{"id":"e5b9f28d0bbb932a","type":"ui_template","z":"2dc76c16782a0f28","group":"${group_left}","name":"Instruction","order":0,"width":0,"height":0,"format":"<!-- based on: YouTube Iframe-API https://developers.google.com/youtube/iframe_api_reference -->\n\n\n<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->\n<div id=\"{{'my_'+$id}}\"></div>\n\n<script>\n\nvar global;\n\n\n// 2. This code loads the IFrame Player API code asynchronously.\n(function addYouTubeIframeAPI()\n{\n  // add YouTubeIframeAPI if not integrated yet\n  if(document.getElementById(\"YouTubeIframeAPI\")==null)\n  {\n    var tag = document.createElement('script');\n    tag.src = \"https://www.youtube.com/iframe_api\";\n    tag.async = true;\n    tag.id='YouTubeIframeAPI';\n    var firstScriptTag = document.getElementsByTagName('script')[0];\n    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n  }\n})()\n// send event YouTubeIframeAPIReady to all listeners\nfunction onYouTubeIframeAPIReady() {\n  // will be executed when the YouTubeAPI is loaded\n  // this function will be overwriten by every subflow-instance\n  console.log(\"onYouTubeIframeAPIReady\");\n  document.body.dispatchEvent(new Event(\"YouTubeIframeAPIReady\"));\n}\n\n\n(function(scope) {\n  global=scope;\n\n\n  // 3. This function creates an <iframe> (and YouTube player)\n  //    after the API code downloads.\n  scope.player;\n  scope.createPlayer=function(){\n    console.log(\"YouTubeIframeAPIReady: create player\");\n    scope.player = new YT.Player(\"my_\"+scope.$id, {\n      height: '324', //'443',\n      width: '100%',\n      //videoId: 'M7lc1UVf-VE',\n      events: {\n        'onReady': scope.onPlayerReady,\n        'onStateChange': scope.onPlayerStateChange\n      }\n    })\n  }\n  /*\n  if(typeof YT != \"undefined\" && typeof scope.player==\"undefined\")\n  {\n    scope.createPlayer();\n  }\n  else\n  { \n  */\n    document.body.addEventListener(\"YouTubeIframeAPIReady\", scope.createPlayer);\n  //}\n\n  // 4. The API will call this function when the video player is ready.\n  scope.onPlayerReady=function(event) {\n    console.log(\"my_\"+scope.$id+\": PlayerReady\");\n    event.target.loadVideoByUrl(scope.url);\n    event.target.playVideo();\n  }\n\n  // 5. The API calls this function when the player's state changes.\n  //    The function indicates that when playing a video (state=1),\n  //    the player should play for six seconds and then stop.\n  var done = false;\n  scope.onPlayerStateChange=function (event) {\n    /*\n    if (event.data == YT.PlayerState.PLAYING && !done) {\n      setTimeout(scope.player.stopVideo(), 6000);\n      done = true;\n    }\n    */\n  }\n\n  scope.url;\n  scope.$watch('msg', function(msg) {\n    if (msg) {\n      // Do something when msg arrives\n\n      if(msg.topic==\"load\")\n      {\n        console.log(\"save video source url\");\n        scope.url=msg.payload;\n        try {\n          //  Block of code to try\n          scope.createPlayer();\n        }\n        catch(error) {\n          //console.error(error);\n        }\n\n      }else\n      {\n        switch(msg.topic)\n        {\n          case \"repeat\":\n            console.log(\"repeat\");\n            scope.player.playVideo();\n            break;\n          default:\n            // code block\n        }\n      }\n    }\n  });\n})(scope);\n</script>\n\n\n\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":610,"y":260,"wires":[[]]},{"id":"baa49d6ec9eaa16e","type":"ui_template","z":"2dc76c16782a0f28","group":"${group_left}","name":"","order":0,"width":0,"height":0,"format":"<script>\nthis.scope.send({topic: \"load\"});\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":315,"y":260,"wires":[["392d5f147552d538"]],"l":false},{"id":"392d5f147552d538","type":"function","z":"2dc76c16782a0f28","name":"Get youtube-iframe","func":"msg.payload=env.get(\"youtube-iframe-url\");\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":260,"wires":[["e5b9f28d0bbb932a"]]},{"id":"21eebace1fcd43b2","type":"ui_text","z":"2dc76c16782a0f28","group":"${group_left}","order":0,"width":0,"height":0,"name":"Instruction","label":"${instruction}","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":16,"color":"#000000","x":410,"y":220,"wires":[]},{"id":"7304c8ccf3e22593","type":"subflow:2dc76c16782a0f28","z":"64266d5c05f7d609","name":"Learning episode 1: Creating a flow","env":[{"name":"tab","value":"a0acaf95295b58ad","type":""},{"name":"group_left","value":"4744e47c0d8d47ad","type":""},{"name":"group_right","value":"d9526704bc06be8c","type":""},{"name":"youtube-iframe-url","value":"https://www.youtube.com/embed/46Ak61c_ymc?si=Dy8L3wT2W0yNnebG","type":"str"},{"name":"instruction","value":"CREATING A FLOW","type":"str"},{"name":"youtube-videolink","value":"<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/veiNb6Y0ERg?si=7Cte0-OQLVTr_WiR\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>","type":"str"},{"name":"videolink","value":"https://www.youtube.com/embed/46Ak61c_ymc?si=Dy8L3wT2W0yNnebG","type":"str"},{"name":"base","value":"7b4c2d84ca99dc9d","type":"str"}],"x":260,"y":160,"wires":[["3cb411f8d023d01f"],[]]},{"id":"3cb411f8d023d01f","type":"subflow:2dc76c16782a0f28","z":"64266d5c05f7d609","name":"Learning episode 2: Wiring Nodes","env":[{"name":"tab","value":"83034514a78cdb54","type":""},{"name":"group_left","value":"9efe9d26038fa7a3","type":""},{"name":"group_right","value":"c40f1eae8e806f12","type":""},{"name":"youtube-iframe-url","value":"https://www.youtube.com/embed/_ST7fiBLlfw?si=i1va2v-3eCS_lz4z","type":"str"},{"name":"instruction","value":"WIRING NODES","type":"str"},{"name":"youtube-iframe","value":"https://www.youtube.com/embed/_ST7fiBLlfw?si=i1va2v-3eCS_lz4z","type":"str"},{"name":"youtube-videolink","value":"<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/_ST7fiBLlfw?si=MzYsQJnpSApQgjRw\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>","type":"str"},{"name":"videolink","value":"https://vod-progressive.akamaized.net/exp=1714496707~acl=%2Fvimeo-prod-skyfire-std-us%2F01%2F1878%2F19%2F484391248%2F2169120044.mp4~hmac=c8eeff945e178d1d1f282a3f118492c604ad0022b3823b2a39fc884820073712/vimeo-prod-skyfire-std-us/01/1878/19/484391248/2169120044.mp4","type":"str"},{"name":"base","value":"7b4c2d84ca99dc9d","type":"str"}],"x":640,"y":160,"wires":[[],["7304c8ccf3e22593"]]},{"id":"2cc22929a47b82b3","type":"ui_template","z":"64266d5c05f7d609","group":"d783cb980c3979fc","name":"Node-Red Dashboard Custom CSS","order":1,"width":0,"height":0,"format":"<!--\nDeveloper: n3odym3\nSource: https://github.com/n3odym3/Node-RED_Dashboard_Themes\n-->\n\n\n<style>\n\n    /*Main background*/\n    body {\n        background: -webkit-linear-gradient(\n            55deg,\n            #009785 0%,\n            #245aa5 50%,\n            /* #b800e9 100%*/\n            /*#00dde9 100%*/\n            #adfff7 100%\n            );\n    }\n\n    /*Top bar*/\n    body.nr-dashboard-theme md-toolbar,\n    body.nr-dashboard-theme md-content md-card {\n        background-color: transparent !important;\n        color: #FFFFFF !important;\n    }\n\n\n    /*Left menu*/\n    /*Sidebar*/\n    body.nr-dashboard-theme md-sidenav {\n        color: white !important;\n        background-color: rgba(0,0,0,0) !important;\n    }\n    /*Hover selection*/\n    a.md-no-style, button.md-no-style {\n        border-radius: 10px !important;\n    }\n    /*Selected*/\n    .nr-menu-item-active div button {\n        border-right: 4px solid rgb(41 98 255) !important;\n    }\n    /*List*/\n    body.nr-dashboard-theme md-sidenav div.md-list-item-inner {\n        color: white !important;\n        background-color: rgba(40,85,165,1) !important;\n        border-radius: 10px !important;\n    }\n\n\n    /*Groups*/\n    ui-card-panel {\n        background-color: rgba(255,255,255,0.1) !important;\n        border-radius: 10px !important;\n    }\n    .nr-dashboard-theme ui-card-panel {\n        border: none !important;\n    }\n    /*Name groups*/\n    .nr-dashboard-theme ui-card-panel p.nr-dashboard-cardtitle {\n        color: rgba(255, 255, 255, 0.5) !important;\n    }\n\n\n    /*Bouttons*/\n    .nr-dashboard-theme .nr-dashboard-button .md-button {\n        background-color: rgba(255,255,255,.1) !important;\n    }\n    .md-button {\n        border-radius: 10px !important;\n    }\n\n\n    /*Dropdown menu*/\n    .nr-dashboard-theme md-select-menu{\n        background-color: rgba(40,85,165,1) !important;\n    }\n    .nr-dashboard-theme md-select-menu, .nr-dashboard-theme md-select-menu md-option {\n        background-color: transparent !important;\n    }\n    .nr-dashboard-theme .md-select-menu-container {\n        border-radius: 10px !important;\n        border: none !important;\n    }\n    .nr-dashboard-theme md-select-menu md-option[selected] {\n        color: white !important;\n        background-color: #1a3566 !important;\n    }\n\n    /*Template*/\n    md-card>img, md-card>md-card-header img, md-card md-card-title-media img {\n        border-radius: 10px !important;\n    }\n\n    /* Color picker text BG */\n    .nr-dashboard-theme .color-picker-input-wrapper > input {\n        color: white !important;\n        background-color: transparent !important;\n    }\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","className":"","x":260,"y":100,"wires":[[]]},{"id":"d783cb980c3979fc","type":"ui_group","name":"Demo","tab":"0449b3b1fea99d52","order":1,"disp":true,"width":"6","collapse":false},{"id":"0449b3b1fea99d52","type":"ui_tab","name":"Demo","icon":"dashboard","disabled":false,"hidden":false}]

For the subflow-instance "Learning episode 1: Creating a flow" you should see

  • tab "Learning episode 1",
  • group_left "Controls",
  • group_right "Instructions",

but you don't.

Best regards, Andreas

1 Like

Edit - Sorry, ignore this!

This simple flow might reasonably be expected to make "outputfile" a precise duplicate of "inputfile" and indeed the debug output suggests that it does.

[{"id":"e80cac0d72f2403e","type":"file in","z":"c39b66ab0a412ca4","name":"","filename":"/home/pi/inputfile","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":530,"y":100,"wires":[["822d34e449703e1a","05062430139f4f92"]]},{"id":"05062430139f4f92","type":"file","z":"c39b66ab0a412ca4","name":"","filename":"/home/pi/outputfile","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":750,"y":100,"wires":[["39cae6503e5aaea5"]]},{"id":"df6dafb983421621","type":"inject","z":"c39b66ab0a412ca4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":340,"y":100,"wires":[["e80cac0d72f2403e"]]},{"id":"822d34e449703e1a","type":"debug","z":"c39b66ab0a412ca4","name":"Inputfile","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":520,"y":140,"wires":[]},{"id":"39cae6503e5aaea5","type":"debug","z":"c39b66ab0a412ca4","name":"Outputfile","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":720,"y":140,"wires":[]}]

However, it actually adds an extra new line character to "outputfile".

pi@AlsoPi:~ $ od -c inputfile
0000000   l   i   n   e   1  \n   l   i   n   e   2  \n
0000014
pi@AlsoPi:~ $ od -c outputfile
0000000   l   i   n   e   1  \n   l   i   n   e   2  \n  \n
0000015

This was discussed a couple of years ago when it was suggested it might be changed for Node-red v4

Can you share the actual flow json? Or link to where it was previously discussed?

Post above amended to include flow.

Original discussion

I am no longer sure about this! The write file node does have "add newline" ticked...

But the debug output is deceptive.

The help text for the node says
On completion of write, input message is sent to output port.
So it outputs what you passed in, not what is written to the file.