Unconnected junction nodes

We've all run into this: a junction node sits right on top of a wire — visually it looks like part of the flow — but it isn't actually connected to it. Finding these cases currently takes a lot of effort, because nothing in the editor distinguishes a junction that's properly wired from one that merely overlaps a wire.

The nasty case is a junction with exactly one connection (one wire in and nothing out, or vs.). A junction with zero connections is at least somewhat visible, since it usually sits on its own. But a single-connection junction that happens to lie over another wire is practically impossible to spot by eye — and Ctrl+F doesn't help either, because junctions have no name or properties to match against.

It would be very user friendly if such cases could be more easily identifiable. Two possible directions (either would solve it):

  1. A way to find junction nodes by their number of connections — the most useful being 0 and 1. This could live in the search, or as a "validate flows"-style check.
  2. Or, junctions with fewer than two connections could be rendered with a visual marker (for example a highlight, a different color, or a red exclamation mark), so they stand out on the canvas.

For context: right now the only approach I've found is to parse flows.json and count, for each junction, how often its ID appears in other nodes' wires (plus subflow in/out/status ports). That identifies which junctions are affected — but it still doesn't help me locate them in the editor: a junction has no name, so Ctrl+F won't find it by ID, and there are no visible coordinates to navigate to. In practice I have to trace back to a named upstream node to even find the junction on the canvas.

Optional, more general idea: the mechanism described in option 1 could be extended to any node type, not just junctions. For most standard nodes this is less interesting, but for function nodes it would be handy — e.g. quickly finding function nodes with no outgoing wire, which are often a forgotten or dead branch. The junction case above would just be one specific use of that more general capability.

Thanks!

This has come up a few times and I too have fallen for that trap.

There is a fan node - node-red-contrib-fan - which was the father of this.

Things I have learnt:
1 - don't put junction nodes over existing wires so as to make it look like it is using those wires.
2 - When connecting to a junctionnode: after connecting to it, drag the node to be sure all observed wires move too. This then detects any wires not connected.

The fan node has a couple of nice things:
1 - It is easier to see
2 - you can expand it's size and put a Name in it to help identify what all the wires are doing.

Say you have all the wires that are to do Function x (yeah, I can't think just now of a better name) that you have Joined together with the fan node to go elsewhere - more tidily that all the individual wires - you put the text function x in the name of the fan node.
Then when looking at the flow, you see the fan node with all the wires and it is named.
So you know what all those wires are doing and it (kinda) helps see things more clearly.

That's prevention, not detection — the request is about detecting existing cases. The drag-test doesn't really work when you have several hundred junction nodes and don't know whether any is broken at all, let alone which one.

How are you expecting the editor to know that you want wires connected to a junction node or not?

That the node is on top of the wire doesn't mean a thing to the editor.

I fear you have your work cut out for yourself.

Why could the editor not detect a wire passing directly under a node I/O connection?

This JSONata expression will return all junction Id's that have one in or out wire and with no wires at all connected
eg

[{"id":"7e18efe9337808ab","type":"inject","z":"613df62afc8a16bf","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"id\":\"5aeabf8c4a8bdef8\",\"type\":\"inject\",\"z\":\"613df62afc8a16bf\",\"name\":\"\",\"props\":[{\"p\":\"payload\"},{\"p\":\"topic\",\"vt\":\"str\"}],\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":120,\"y\":885,\"wires\":[[\"d96b6507558b093b\"]]},{\"id\":\"928fb8d89f07a80a\",\"type\":\"debug\",\"z\":\"613df62afc8a16bf\",\"name\":\"debug 18\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":450,\"y\":675,\"wires\":[]},{\"id\":\"5c12d62b5db0e661\",\"type\":\"inject\",\"z\":\"613df62afc8a16bf\",\"name\":\"\",\"props\":[{\"p\":\"payload\"},{\"p\":\"topic\",\"vt\":\"str\"}],\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":210,\"y\":735,\"wires\":[[\"062592ee003bc92b\",\"60dfb6ccede5d405\"]]},{\"id\":\"b56e0f71070c9bb9\",\"type\":\"debug\",\"z\":\"613df62afc8a16bf\",\"name\":\"debug 29\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":600,\"y\":795,\"wires\":[]},{\"id\":\"270b4fe377d19bdf\",\"type\":\"junction\",\"z\":\"613df62afc8a16bf\",\"x\":300,\"y\":645,\"wires\":[[\"928fb8d89f07a80a\"]]},{\"id\":\"062592ee003bc92b\",\"type\":\"junction\",\"z\":\"613df62afc8a16bf\",\"x\":300,\"y\":735,\"wires\":[[]]},{\"id\":\"60dfb6ccede5d405\",\"type\":\"junction\",\"z\":\"613df62afc8a16bf\",\"x\":285,\"y\":705,\"wires\":[[\"928fb8d89f07a80a\"]]},{\"id\":\"180e8f16269453a8\",\"type\":\"junction\",\"z\":\"613df62afc8a16bf\",\"x\":345,\"y\":660,\"wires\":[[]]},{\"id\":\"d96b6507558b093b\",\"type\":\"change\",\"z\":\"613df62afc8a16bf\",\"name\":\"\",\"rules\":[{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"\",\"tot\":\"str\"}],\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":235,\"y\":930,\"wires\":[[\"9aedbcd00a762307\"]]},{\"id\":\"9aedbcd00a762307\",\"type\":\"debug\",\"z\":\"613df62afc8a16bf\",\"name\":\"debug 28\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":420,\"y\":915,\"wires\":[]}]","payloadType":"json","x":185,"y":825,"wires":[["0ccf1b06d397e110"]]},{"id":"0ccf1b06d397e110","type":"change","z":"613df62afc8a16bf","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"(\t   $junc := $$.payload[type = \"junction\"];\t   $ids := $$.payload.wires[0];\t   $junc[$not($.id in $ids) or $count($.wires[0])<1].id\t)\t    ","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":825,"wires":[["928fb8d89f07a80a"]]},{"id":"928fb8d89f07a80a","type":"debug","z":"613df62afc8a16bf","name":"debug 18","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":450,"y":675,"wires":[]}]

You would have to load your flow.json using a file read node.

Nice, that's much closer to what I'm after — thanks! Two caveats I hit when doing the same via a script, though:

  • wires[0] only checks the first output (so a junction fed only by e.g. output 1 of a switch is a false positive), and subflow in/out/status connections aren't in the wires array either.
  • But the bigger point: this still only tells me which junction IDs are affected — it doesn't help me locate them on the canvas, since a junction has no name for Ctrl+F and there are no coordinates to jump to. That last step is really the core of the request.

Junctions only have one output, this is what wires[0] is searching for on the output of only the junction nodes.

Once you have the id's you can search for them in the info sidebar.

Looks like the search issue was not fixed my mistake. How to find a Junction - #11 by cymplecy

Nice use of Jsonata once again @E1cid.
I've been struggling all morning to get chatgpt to give me a working Jsonata expression!

Edit: The flow I first posted failed to notice junctions linked to from the second output of a node.
I think this is better, no Jsonata any more :sweat_smile:

[{"id":"ab16fa6f24cb59e6","type":"tab","label":"Disconnected junctions","disabled":false,"info":"","env":[]},{"id":"f678564d8f5fe523","type":"group","z":"ab16fa6f24cb59e6","name":"Test Nodes","style":{"label":true},"nodes":["71fbd21251a2b603","025d2ddbe90f41f1","bfe0dc550fd5f618","804e1f7280eec964","452812f6f4ce7081","820a5e11855cc166","ed4316537e081e33"],"x":614,"y":74,"w":352,"h":132},{"id":"025d2ddbe90f41f1","type":"junction","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","x":640,"y":120,"wires":[[]]},{"id":"bfe0dc550fd5f618","type":"junction","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","x":640,"y":140,"wires":[["71fbd21251a2b603","ed4316537e081e33"]]},{"id":"804e1f7280eec964","type":"junction","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","x":940,"y":100,"wires":[[]]},{"id":"452812f6f4ce7081","type":"junction","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","x":940,"y":140,"wires":[[]]},{"id":"820a5e11855cc166","type":"junction","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","x":940,"y":180,"wires":[[]]},{"id":"ed4316537e081e33","type":"junction","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","x":760,"y":100,"wires":[["804e1f7280eec964"]]},{"id":"231f8b2a169ba37b","type":"junction","z":"ab16fa6f24cb59e6","x":840,"y":100,"wires":[[]]},{"id":"bcd4bc8e5fa549df","type":"file in","z":"ab16fa6f24cb59e6","name":"Scan flows file","filename":"path","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":280,"y":80,"wires":[["5989a3158b6f6489"]]},{"id":"5989a3158b6f6489","type":"json","z":"ab16fa6f24cb59e6","name":"","property":"payload","action":"","pretty":false,"x":510,"y":80,"wires":[["d01ad68650990728"]]},{"id":"d01ad68650990728","type":"function","z":"ab16fa6f24cb59e6","name":"Find tab names & junctions","func":"let tablist = {}\nlet junctionlist = {}\nfor (let i in msg.payload) {\n    const id = msg.payload[i].id\n    if (msg.payload[i].type == \"tab\") {\n        tablist[id] = msg.payload[i].label\n    }\n    if (msg.payload[i].type == \"junction\") {\n        junctionlist[id] = msg.payload[i]\n        junctionlist[id].incomingwires = false\n        junctionlist[id].outgoingwires = false\n    }\n}\nflow.set(\"tablist\", tablist)\nflow.set(\"junctionlist\", junctionlist)\nreturn msg","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":140,"wires":[["331897a2ac770c97"]]},{"id":"012b5103d31885d9","type":"json","z":"ab16fa6f24cb59e6","name":"","property":"payload","action":"","pretty":false,"x":510,"y":200,"wires":[["6dbb7671391fd1de"]]},{"id":"331897a2ac770c97","type":"file in","z":"ab16fa6f24cb59e6","name":"Rescan flows file","filename":"path","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":290,"y":200,"wires":[["012b5103d31885d9"]]},{"id":"6dbb7671391fd1de","type":"function","z":"ab16fa6f24cb59e6","name":"find junctions' incoming wires","func":"let junctionlist = flow.get('junctionlist') || {}\nconst tablist = flow.get('tablist') || {}\n\nfor (let i in msg.payload) {            // EACH NODE IN FLOW FILE\n    const id = msg.payload[i].id\n    if (msg.payload[i].type == \"junction\") {  // If it's a junction, update tab name\n        const tabid = msg.payload[i].z\n        junctionlist[id].tab = tablist[tabid]\n    }\n\n    if (msg.payload[i].wires && msg.payload[i].wires.length > 0) {  // If it has outgoing wires\n        let wires = msg.payload[i].wires        \n        for (let j=0; j< wires.length; j++) {    // Each node output\n            let linkids = wires[j]\n            for (let k=0; k<linkids.length; k++) {   // Each linked ID\n                const linkid = linkids[k]\n                if (junctionlist[linkid]) { // This node is wired to a junction, update junction incomingwires\n                    junctionlist[linkid].incomingwires = true;\n                }\n                if (msg.payload[i].type == \"junction\") { // This node is a junction AND has outgoing wire[s]\n                    junctionlist[id].outgoingwires = true;\n                }\n            }\n        }\n    }\n}\n\nflow.set('junctionlist', junctionlist);\nlet newmsg = {}\nnewmsg.payload = {\"junctionCount\": 0, \"neither\":[], \"noIncoming\":[], \"noOutgoing\":[]}\nfor (let i in junctionlist) {\n    const usefuldata ={\n        \"id\":junctionlist[i].id,\n        \"tab\":junctionlist[i].tab,\n        \"x\": junctionlist[i].x,\n        \"y\": junctionlist[i].y\n    }\n    if (junctionlist[i].incomingwires === false && junctionlist[i].outgoingwires === false) {\n        newmsg.payload.neither.push(usefuldata)\n    }\n    else if (junctionlist[i].incomingwires === false) {\n        newmsg.payload.noIncoming.push(usefuldata)\n    }\n    else if (junctionlist[i].outgoingwires === false) {\n        newmsg.payload.noOutgoing.push(usefuldata)\n    }\n    newmsg.payload.junctionCount ++\n}\nreturn newmsg;\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":260,"wires":[["b4bc4f8df7dfa00b"]]},{"id":"b4bc4f8df7dfa00b","type":"debug","z":"ab16fa6f24cb59e6","name":"debug 10","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":680,"y":260,"wires":[]},{"id":"f492b248145e9d02","type":"inject","z":"ab16fa6f24cb59e6","name":"flows file","props":[{"p":"path","v":"/home/pi/.node-red/flows.json","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":120,"y":80,"wires":[["bcd4bc8e5fa549df"]]},{"id":"71fbd21251a2b603","type":"change","z":"ab16fa6f24cb59e6","g":"f678564d8f5fe523","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":820,"y":140,"wires":[["804e1f7280eec964","452812f6f4ce7081","820a5e11855cc166"]]}]

15 junctions, but only 6 of them not connected (all in Test nodes on this flow)

That is indeed a problem. I've also played around with orphan nodes (as I called them) but in my implementation, I ignored junctions for exactly that reason - there was no way to highlight them.

There is also no jQuery triggery to hack around that because junction nodes do not have an identifying id within the workspace, i.e. $('#<node-id>') will give you access to any node but not junction.

Node-RED lint (GitHub - node-red/nrlint: Node-RED Flow Linter · GitHub) does already have a rule to report unconnected nodes...

Locate under-connected junctions (console helper)

Two things in one here: first, a solution to find and visually mark junctions anywhere in Node-RED — across all tabs and subflows. And second, this doubles as a working proof of concept for my feature request issue #4967: it shows that locating and highlighting junctions is achievable, even though there's currently no built-in way to do it.

The script finds junctions with fewer than 2 connections (dead/under-connected ones), switches to the right tab, scrolls the junction into view and draws a pulsing marker directly on it — so you actually see where it is, not just its ID. It counts incoming + outgoing wires as well as subflow in/out/status ports. (Dropping the connection-count filter turns it into a "mark every junction" tool — the locate/highlight mechanism is the same.)

Animation

It's a read-only helper — it only reads the flow and draws a marker on screen, it never modifies anything, so there's nothing to accidentally break.

How it works: it reads the full flow via RED.nodes.createCompleteNodeSet(), computes each junction's connection count, then locates the elements on the canvas using d3's bound data (__data__) — which sidesteps the fact that junctions have no id-addressable DOM node. It auto-switches tabs and centers each hit with a pulsing marker.

Usage: open the Node-RED editor, open the browser DevTools console, paste the script.
(If Node-RED runs inside an iframe, e.g. the Home Assistant add-on, select the Node-RED frame in the console's context dropdown first — otherwise you'll get RED is not defined.)

Controls:

  • next() / prev() — step through manually
  • goTo(n) — jump to result n
  • play() or play(1000) — auto-advance (ms per step)
  • stop() — halt auto-advance
  • clearRing() — remove the marker

Re-running the script is safe: it stops and replaces any previous instance automatically.

(function(){
  // --- Self-protect: kill any previous instance before starting a new one ---
  if(window.__jfLocator && typeof window.__jfLocator.destroy==='function'){
    window.__jfLocator.destroy();
  }

  if(typeof RED.nodes.createCompleteNodeSet!=='function'){
    console.warn('RED.nodes.createCompleteNodeSet is missing – let me know and I will use another approach.');return;
  }
  if(!document.getElementById('jf-loc-style')){
    const s=document.createElement('style'); s.id='jf-loc-style';
    s.textContent='@keyframes jfpulse{0%{transform:translate(-50%,-50%) scale(.5);opacity:1}100%{transform:translate(-50%,-50%) scale(1.8);opacity:.1}}';
    document.head.appendChild(s);
  }

  // 1. Get the complete flow set (same shape as flows.json, incl. junctions & subflows)
  const flows=RED.nodes.createCompleteNodeSet();

  // Container labels (tabs + subflows)
  const label={};
  flows.forEach(n=>{
    if(n.type==='tab') label[n.id]=n.label||('Tab '+n.id);
    if(n.type==='subflow') label[n.id]='Subflow: '+(n.name||n.id);
  });

  // 2. Count degree: incoming + outgoing wires, plus subflow in/out/status ports
  const grad={}; const add=id=>{ if(id) grad[id]=(grad[id]||0)+1; };
  flows.forEach(n=>{
    if(Array.isArray(n.wires)) n.wires.forEach(o=>{ if(Array.isArray(o)) o.forEach(z=>{ add(n.id); add(z); }); });
    if(n.type==='subflow') ['in','out','status'].forEach(k=>{
      const arr=Array.isArray(n[k])?n[k]:(n[k]?[n[k]]:[]);
      arr.forEach(p=>(p.wires||[]).forEach(w=>add(w.id)));
    });
  });

  // 3. Collect junctions with degree < 2
  const list=[];
  flows.forEach(n=>{ if(n.type==='junction'){ const g=grad[n.id]||0; if(g<2)
    list.push({id:n.id,z:n.z,x:n.x,y:n.y,grad:g,tab:label[n.z]||n.z}); }});

  // Summary
  const byTab={}; list.forEach(j=>{(byTab[j.tab]=byTab[j.tab]||[]).push(j);});
  console.log('Suspect junctions (connections < 2): '+list.length);
  Object.keys(byTab).forEach(t=>console.log('   '+t+': '+byTab[t].length));

  // --- Navigation state ---
  const sleep=ms=>new Promise(r=>setTimeout(r,ms));
  const getSvg=()=>document.querySelector('#red-ui-workspace-chart svg')||document.querySelector('svg');
  function findEl(id){ let f=null; getSvg().querySelectorAll('g').forEach(g=>{ if(g.__data__&&g.__data__.id===id) f=g; }); return f; }

  let cur=null, idx=-1, timer=null;

  function ring(el,text){
    const r=el.getBoundingClientRect(), cx=r.left+r.width/2, cy=r.top+r.height/2, size=70;
    const m=document.createElement('div');
    m.className='jf-loc-ring';
    Object.assign(m.style,{position:'fixed',left:cx+'px',top:cy+'px',width:size+'px',height:size+'px',
      border:'3px solid #e00',borderRadius:'50%',boxShadow:'0 0 0 3px rgba(238,0,0,.4),0 0 14px 5px rgba(238,0,0,.55)',
      pointerEvents:'none',zIndex:99999,animation:'jfpulse 1s ease-out infinite'});
    const dot=document.createElement('div');
    Object.assign(dot.style,{position:'absolute',left:'50%',top:'50%',width:'9px',height:'9px',
      transform:'translate(-50%,-50%)',background:'#e00',borderRadius:'50%'}); m.appendChild(dot);
    const t=document.createElement('div');t.textContent=text;
    Object.assign(t.style,{position:'absolute',top:'-22px',left:'50%',transform:'translateX(-50%)',
      background:'#e00',color:'#fff',font:'11px monospace',padding:'2px 6px',borderRadius:'4px',whiteSpace:'nowrap'});
    m.appendChild(t);document.body.appendChild(m);return m;
  }

  async function show(i){
    if(cur){cur.remove();cur=null;}
    if(i<0||i>=list.length){console.log('Reached the end.');stopTimer();return false;}
    idx=i; const j=list[i];
    if(RED.workspaces.active()!==j.z){ RED.workspaces.show(j.z); await sleep(400); }
    const el=findEl(j.id);
    if(!el){console.warn('Element not found (subflow may need to be opened):',j.id,'on',j.tab);return true;}
    el.scrollIntoView({block:'center',inline:'center'}); await sleep(250);
    cur=ring(el,(i+1)+'/'+list.length+'  ['+j.tab+']  degree '+j.grad+'  '+j.id);
    console.log((i+1)+'/'+list.length,'['+j.tab+']','degree '+j.grad,j.id,'x='+j.x,'y='+j.y);
    return true;
  }

  function stopTimer(){ if(timer){clearInterval(timer);timer=null;console.log('Stopped at '+(idx+1)+'/'+list.length);} }

  // --- Public API ---
  window.next=()=>{stopTimer();show(idx+1);};
  window.prev=()=>{stopTimer();show(idx-1);};
  window.goTo=n=>{stopTimer();show(n-1);};
  window.clearRing=()=>{stopTimer();if(cur){cur.remove();cur=null;}};
  window.play=(ms)=>{ stopTimer(); const step=ms||1200; if(idx>=list.length-1) idx=-1;
    (async()=>{ await show(idx+1);
      timer=setInterval(async()=>{ if(!await show(idx+1)) stopTimer(); }, step);
      console.log('play running ('+step+'ms). Stop with  stop()'); })(); };
  window.stop=stopTimer;

  // --- Cleanup handle used for self-protect on re-run ---
  window.__jfLocator={
    destroy(){
      try{ if(timer){clearInterval(timer);timer=null;} }catch(e){}
      try{ if(cur){cur.remove();cur=null;} }catch(e){}
      document.querySelectorAll('.jf-loc-ring').forEach(d=>d.remove());
      ['next','prev','goTo','play','stop','clearRing'].forEach(f=>{ try{delete window[f]}catch(e){window[f]=undefined} });
    }
  };

  if(!list.length){console.log('Nothing to do – every junction has >= 2 connections.');return;}
  console.log('Manual: next() | prev() | goTo(n) | clearRing()   Auto: play() / play(1000) | stop()');
  show(0);
})();

Please note this is not meant as the solution — just a proof of concept that finding and highlighting junctions is achievable. A proper implementation belongs in the editor (and in search), which is what this feature request is about.

Does it have an integrated mode inside Node-RED? I.e. a visualisation of its results?

Alright, thought it would just take a moment ... then it was a little longer:

Screen Recording 2026-07-01 at 17.57.03-2

Since I already created the "orphan" list but excluded junctions, this time I thought I'd add junctions. The highlight was a pain but got it working using jQuery trickery:

let junc = RED.nodes.junction(ndeid)
$($(`g[transform="translate(${junc.x},${junc.y})"] rect`)[0]).addClass('red-ui-flow-junction-hovered') 
$(`g[transform="translate(${junc.x},${junc.y})"]`).addClass('selected') 
RED.view.select({ nodes: [junc] })
zoomToNodeOrJunction(junc)

the zoomToNodeOrJunction is a bastardisation of reveal.

The animation above is from 0.15.4 of the introspection package and the code probably only works with NR5. Shown is the "Unconnected" lint feature that shows a list of all nodes that aren't connected (either in or out). The search box allows for type:junction to limit the results to only junctions and the flow:XXX (or flow:current) can further limit the result set.

One click on the entry and the workspace with the node/junction is shown. Double click on the entry and the workspace is zoomed to the node/junction and the node/junction is highlighted. In the case of the junction node, that's the CSS trickery shown above.

The RED.view.select(..) updates the info box with the junctions details.

Use at own risk.