Keep a member's link inside their own team, and stop the dashboard contradicting the admin

Two faults found chasing why Terry (#840) was routing joins to #148, a
position outside his team.

The leg lookup only considered positions on the curated rotation list.
Terry qualified the same day he joined, so his two directs (#843, #844)
were too new to be on it. With no leg match the code fell straight through
to the GLOBAL company rotation — sending his people to a stranger while
his own directs sat on 0/2. That is the opposite of the team-first
doctrine the moving link exists to serve. There is now a last-resort step
inside the leg: the nearest position still needing its 2, curated or not.
The curated order still wins when it applies; the global rotation is now
only reached when the member's whole leg really is qualified.

Second, the dashboard ignored directDefault entirely. Marty had already
set Terry to direct placement in the admin, which the join page honours —
but Terry's own share panel still told him his link routed to someone
else. Two different answers to "where do my people land". The panel now
says rotation is off and every join lands under him, and explains
?direct=0 for the exception.

Also made the all-qualified case name what it is: joins go to the company
rotation, placed wherever the company is filling, not inside your leg.
That wording is what made #148 look like a bug rather than a fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-18 09:08:24 -05:00
parent 2f4b70c886
commit 1284d28891
2 changed files with 18 additions and 4 deletions
+11 -4
View File
@@ -794,13 +794,20 @@
const dashUrl=`https://rmcircle.team/join/${d.id}`;
const directUrl=`${dashUrl}?direct=1`;
const q2=d.directCount>=2, nx=d.nextInLine;
// A position on the admin's direct-placement list has its moving link switched OFF:
// /join/<id> behaves as ?direct=1, so every join lands under THIS position. The join
// page has always honoured that; this panel did not, and told the member their link
// routed to someone else. Two different answers about where their people land.
const dd=!!d.directDefault;
// The personal QR is ALWAYS shown: /join/<id> self-rotates via the moving
// link, so a scan is always placed correctly — even after 2/2. This is the
// person-to-person flow: open your page, tap the QR, they scan, they see
// your pitch page with the video and the join button.
const guidance=q2
const guidance=dd
?`${q2?'<span class="q-badge q-yes" style="margin-right:6px">★ Qualified</span>':''} Rotation is <strong style="color:var(--text)">off</strong> for this position: every join through your page lands <strong style="color:var(--gold)">directly under you</strong> as depth, and the entry reward is yours. That is a deliberate setting on your position — ask your sponsor before changing how you promote it.`
:q2
?(nx?`<span class="q-badge q-yes" style="margin-right:6px">★ Qualified</span> Your page now routes new joins to <strong style="color:var(--text)">#${nx.id}</strong> (${nx.directCount||0}/2) — the team play moving down your leg automatically. Anyone who scans still lands on YOUR pitch page; placement is handled for you. A signup on your own link is always a bonus: the entry reward is yours.`
:`<span class="q-badge q-yes" style="margin-right:6px">★ Qualified</span> Your whole leg is qualified — your page routes new joins to the team rotation automatically.`)
:`<span class="q-badge q-yes" style="margin-right:6px">★ Qualified</span> Your whole leg is qualified — your page routes new joins to the <strong style="color:var(--text)">company rotation</strong>, so the next join is placed wherever the company is currently filling, not inside your own leg.`)
:`Share this with your 2 — anyone who scans or clicks lands on your personal pitch page (video, live proof, join button) and joins under <strong style="color:var(--gold)">you</strong>.`;
el.innerHTML=`<p style="color:var(--muted);font-size:14px;line-height:1.6;margin:6px 0 14px">${guidance}</p>
<div style="display:flex;gap:18px;align-items:center;flex-wrap:wrap">
@@ -814,9 +821,9 @@
</div>
<div style="margin-top:16px;padding-top:14px;border-top:1px solid var(--line)">
<div style="font-weight:800;font-size:14px;margin-bottom:4px">🔀 Two ways to share</div>
<p class="micro" style="margin:0 0 8px">The link above is your <strong style="color:var(--text)">Team link</strong> ${q2?'— now that youre qualified it routes new joins to the next teammate who needs directs, building your team in order (recommended).':'— it credits you and builds your first two.'} Want new people to land <strong style="color:var(--text)">under YOU</strong> as spillover instead? Use your <strong style="color:var(--text)">Direct link</strong>:</p>
<p class="micro" style="margin:0 0 8px">${dd?'Rotation is switched off for this position, so the link above already places every join <strong style="color:var(--text)">under you</strong>. To send someone into the team rotation instead, add <strong style="color:var(--text)">?direct=0</strong> to it.':`The link above is your <strong style="color:var(--text)">Team link</strong> ${q2?'— now that youre qualified it routes new joins to the next teammate who needs directs, building your team in order (recommended).':'— it credits you and builds your first two.'} Want new people to land <strong style="color:var(--text)">under YOU</strong> as spillover instead? Use your <strong style="color:var(--text)">Direct link</strong>:`}</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><button id="copyDirect" class="btn btn-secondary btn-sm">Copy Direct link</button><span class="micro" style="word-break:break-all;color:var(--muted)">${esc(directUrl)}</span></div>
<p class="micro" style="margin:8px 0 0"><strong style="color:var(--text)">Team link</strong> helps your team qualify. <strong style="color:var(--text)">Direct link</strong> places every join under you (own spillover). Either way you keep the entry reward on anyone you personally bring.</p>
<p class="micro" style="margin:8px 0 0">${dd?'Either way you keep the entry reward on anyone you personally bring.':'<strong style="color:var(--text)">Team link</strong> helps your team qualify. <strong style="color:var(--text)">Direct link</strong> places every join under you (own spillover). Either way you keep the entry reward on anyone you personally bring.'}</p>
</div>`;
const qr=document.getElementById('dQr');if(qr)qr.innerHTML=qrSvg(dashUrl);
const qb=document.getElementById('dQrBtn');
+7
View File
@@ -646,6 +646,13 @@ async function handleApi(req,res,pathname){
let pick=null;
if(act)pick=bfs.find(n=>String(n.id)===String(act.id)&&(n.directCount||0)<2);
if(!pick)pick=bfs.find(n=>participants.has(String(n.id))&&(n.directCount||0)<2);
// Last resort WITHIN the leg: the nearest position that still needs its 2,
// curated list or not. Without this, a member whose directs are too new to be
// in the rotation list had no leg pick at all and fell through to the GLOBAL
// rotation — sending their people to a stranger's position while their own
// brand-new directs sat on 0/2. That is the opposite of team-first. (Terry #840,
// qualified the same day he joined, was routing to #148; 2026-09-18.)
if(!pick)pick=bfs.find(n=>(n.directCount||0)<2);
if(pick)r.nextInLine={id:pick.id,directCount:pick.directCount||0,levelName:pick.levelName};
}catch(e){}
}