Fix tool wall ordering, and make suite-me use the one entitlement helper

Two bugs Marty spotted from a screenshot of his own wall.

1. The Traffic Desk rendered between L5 and L6 under a stray second
   "L1 SCINTILLA" header. The wall emits a section header whenever the level
   changes while walking the TOOLS array, and the Traffic Desk was still in
   its original L5 array slot from before it moved to L1. Now the wall sorts
   by level before rendering, so the array can be edited in any order and
   this class of bug cannot come back.

2. Tiles above L4 still showed "UNLOCKS AT ..." despite suiteLevelOverride.
   /api/public/suite-me carried its OWN copy of the org/allowlist logic
   rather than calling suiteEntitlement, so the override reached every tool
   page but not the wall linking to them. That duplication is the actual
   defect — the route now calls the shared helper, and the override also
   carries levelName so the header cannot disagree with the tiles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-28 11:18:08 -05:00
parent f05d5a2746
commit 7ca89f25ee
2 changed files with 17 additions and 29 deletions
+12 -28
View File
@@ -684,7 +684,8 @@ async function handleApi(req,res,pathname){
});
const ov=ovMap[Number(d.id)];
if(ov&&ov>Number(d.level||0)){
dd=Object.assign({},d,{level:ov,trueLevel:Number(d.level||0),levelOverridden:true});
dd=Object.assign({},d,{level:ov,levelName:LEVELS[ov-1]||d.levelName,
trueLevel:Number(d.level||0),trueLevelName:d.levelName,levelOverridden:true});
}
return {d:dd,inOrg,beta,allowed};
}
@@ -1065,34 +1066,17 @@ async function handleApi(req,res,pathname){
}
if(req.method==='GET'&&pathname==='/api/public/suite-me'){
// The Circle Suite entitlement: signed-in wallet -> live level + org check.
const s=messages.authFromCookie(req);
if(!s)return json(res,401,{error:'Not signed in.'});
try{
const cached=memberCache.get(s.id);
let d;
if(cached&&Date.now()-cached.ts<120000)d=cached.data;
else{
d=await Promise.race([chain.memberPublic(s.id),new Promise((_,rej)=>setTimeout(()=>rej(new Error('timeout')),20000))]);
memberCache.set(s.id,{ts:Date.now(),data:d});
}
if(!d||!d.registered)return json(res,404,{error:'Position not found.'});
const cfg=getConfig();
// teamRootId is a comma-separated LIST of team roots (e.g. "21,137,139").
// Number() on that yields NaN -> every member reads as outside the org,
// which is exactly the bug Marty hit signing in as #21. Parse the list.
const roots=String(cfg.teamRootId||cfg.orgRootId||'21').split(',').map(x=>Number(x.trim())).filter(Boolean);
const chainIds=Array.isArray(d.uplineChain)?d.uplineChain.map(Number):[];
const inOrg=roots.some(r=>Number(d.id)===r||chainIds.includes(r));
// Team-beta gate: while suiteAllowlist is set (comma-separated ids),
// only those positions light up; everyone else sees the beta notice.
// Clearing the allowlist opens the Suite to the whole org - no deploy.
const allow=String(cfg.suiteAllowlist||'').split(',').map(x=>Number(x.trim())).filter(Boolean);
const beta=allow.length>0;
const allowed=!beta||allow.includes(Number(d.id));
return json(res,200,{id:d.id,tier:d.tier,tierName:d.tierName,level:d.level,levelName:d.levelName,directCount:d.directCount,inOrg,beta,allowed});
}catch(e){return json(res,500,{error:'Chain read hiccup — refresh to retry.'});}
// One source of truth for entitlement. This route used to carry its own
// copy of the org/allowlist logic, which is exactly how the level override
// came to work on every tool page but not on the wall that links to them.
const e=await suiteEntitlement(req).catch(()=>({error:'Chain read hiccup - try again.',code:500}));
if(e.error)return json(res,e.code||500,{error:e.error});
const d=e.d;
return json(res,200,{id:d.id,tier:d.tier,tierName:d.tierName,level:d.level,levelName:d.levelName,
directCount:d.directCount,inOrg:e.inOrg,beta:e.beta,allowed:e.allowed,
levelOverridden:!!d.levelOverridden,trueLevel:d.trueLevel});
}
if(req.method==='GET'&&pathname==='/api/public/msg-me'){
const s=messages.authFromCookie(req);
if(!s)return json(res,401,{error:'Not signed in.'});