diff --git a/public/suite.js b/public/suite.js index 4f9a7eb..e4a8de8 100644 --- a/public/suite.js +++ b/public/suite.js @@ -43,7 +43,11 @@ var grid = $('suGrid'); grid.innerHTML = ''; var lastLv = 0; - TOOLS.forEach(function (t) { + // Render in LEVEL order, not array order. The array is maintained by hand + // and a tile whose level changes (the Traffic Desk moved from L5 to L1) + // would otherwise emit a stray section header in the middle of the wall. + // Sorting here means the array can be edited in any order, forever. + TOOLS.slice().sort(function (a, b) { return a.lv - b.lv; }).forEach(function (t) { if (t.lv !== lastLv) { lastLv = t.lv; var h = document.createElement('div'); diff --git a/server.js b/server.js index 770a801..16c5d81 100644 --- a/server.js +++ b/server.js @@ -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.'});