From f5de98c214741c21237126aba53a1e2380c72478 Mon Sep 17 00:00:00 2001 From: martbost Date: Sat, 15 Aug 2026 05:43:03 -0500 Subject: [PATCH] Classify rotation joins by worked-sponsor status, not just active A rotation joiner submits their ID after the rotation may have advanced (esp. with auto-advance: the direct who completes a sponsor's 2/2 joined under it, but the queue has already moved on). Keying "rotation" off the currently-active sponsor mislabeled them "leg" (hit live: Harvey #92 under #36 after #36 qualified). Now: rotation if the on-chain referrer is a queue sponsor that is active OR qualified (was/is worked); a still-waiting or non-queue referrer stays a leg join, preserving the original #49/#56-under-#46 fix. Auto-enqueue now parents the new position to the actual on-chain referrer, not the current active. Co-Authored-By: Claude Fable 5 --- server.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index d81c2e8..a6a406f 100644 --- a/server.js +++ b/server.js @@ -156,12 +156,20 @@ async function handleSubmitId(req, res) { new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 6000)) ]); } catch (e) { onchain = null; } - // the chain decides the path: rotation join (referrer = active rotation sponsor) - // vs leg join (someone's personal team build) — regardless of which page they used - const active = activeSponsor(getSponsors()); + // the chain decides the path: a rotation join is one whose on-chain referrer is + // a rotation-queue sponsor that IS or HAS BEEN worked (status active or + // qualified) — not just the currently-active one. With auto-advance, the direct + // who completes a sponsor's 2/2 (and thus joined under it) submits their ID + // AFTER the rotation has already moved on, so keying off "active only" wrongly + // labeled them a leg join. A referrer that's a still-waiting queue position, or + // not in the queue at all, is a personal leg join. + const sponsorsNow = getSponsors(); + const active = activeSponsor(sponsorsNow); let joinPath = 'unknown'; - if (onchain && onchain.registered) joinPath = (active && String(onchain.referrerId) === String(active.id)) ? 'rotation' : 'leg'; - else if (onchain && !onchain.registered) joinPath = 'notfound'; + if (onchain && onchain.registered) { + const refSp = sponsorsNow.find(s => String(s.id) === String(onchain.referrerId)); + joinPath = (refSp && (refSp.status === 'active' || refSp.status === 'qualified')) ? 'rotation' : 'leg'; + } else if (onchain && !onchain.registered) joinPath = 'notfound'; subs.push({ newId, memberName, sponsorId, source: source||'(direct)', clickid, ts: new Date().toISOString(), path: joinPath, onchain: onchain ? { registered: onchain.registered, tier: onchain.tierName, level: onchain.levelName, referrerId: onchain.referrerId, uplineId: onchain.uplineId } : undefined }); writeJson(SUBMISSIONS_FILE, subs.slice(-1000)); @@ -177,14 +185,14 @@ async function handleSubmitId(req, res) { queueNote = 'Already in the rotation queue.'; } else { const maxOrder = sponsors.reduce((m, s) => Math.max(m, s.sortOrder || 0), 0); - sponsors.push({ id: String(newId), name: memberName, parentId: String(active.id), directs: 0, level: onchain.levelName || 'Scintilla', status: sponsors.some(s => s.status === 'active') ? 'waiting' : 'active', sortOrder: maxOrder + 10, clicks: 0, notes: `auto-added: rotation join ${new Date().toISOString().slice(0, 10)}` }); + sponsors.push({ id: String(newId), name: memberName, parentId: String(onchain.referrerId), directs: 0, level: onchain.levelName || 'Scintilla', status: sponsors.some(s => s.status === 'active') ? 'waiting' : 'active', sortOrder: maxOrder + 10, clicks: 0, notes: `auto-added: rotation join under #${onchain.referrerId} ${new Date().toISOString().slice(0, 10)}` }); sponsors = normalizeStatuses(sponsors); saveSponsors(sponsors); const waitingAhead = sponsors.filter(s => s.status === 'waiting' && (s.sortOrder || 0) < maxOrder + 10).length; queueNote = `Auto-added to the rotation queue (${waitingAhead} waiting ahead of them).`; } } catch (e) { queueNote = `⚠ Auto-add to queue failed (${e.message}) — add manually.`; console.error('queue auto-add', e.message); } - msg = `šŸ”” RM Circle: ROTATION JOIN CONFIRMED āœ…\nName: ${memberName}\nNew ID: ${newId} (${onchain.tierName}, verified on-chain)\nJoined under rotation sponsor: #${onchain.referrerId}\nSource: ${source||'(direct)'}\nāœ… ${queueNote}\n(+1 direct for ID ${active.id} syncs from the chain automatically.)`; + msg = `šŸ”” RM Circle: ROTATION JOIN CONFIRMED āœ…\nName: ${memberName}\nNew ID: ${newId} (${onchain.tierName}, verified on-chain)\nJoined under rotation sponsor: #${onchain.referrerId}\nSource: ${source||'(direct)'}\nāœ… ${queueNote}\n(Sponsor #${onchain.referrerId}'s direct count syncs from the chain automatically.)`; } else if (joinPath === 'leg') { msg = `🌱 RM Circle: TEAM-BUILD JOIN (not rotation)\nName: ${memberName}\nNew ID: ${newId} (${onchain.tierName}, verified on-chain)\nActual sponsor on-chain: #${onchain.referrerId}${sponsorId!=='?'&&String(onchain.referrerId)!==sponsorId?` (form said ${sponsorId})`:''}\nSource: ${source||'(direct)'}\n→ Leg growth under #${onchain.referrerId} — no rotation action needed. Add them to the rotation queue only if they want the team effort.`; } else if (joinPath === 'notfound') {