diff --git a/docs/for-manson-optional-profiles.md b/docs/for-manson-optional-profiles.md new file mode 100644 index 0000000..6463971 --- /dev/null +++ b/docs/for-manson-optional-profiles.md @@ -0,0 +1,88 @@ +# What changed with member profiles, and why + +*A note for Manson before this goes out to the team.* + +## Your objection was right + +You pushed back on requiring a username and a verified email, and the reason you gave was +decentralization: the whole point of building on a public smart contract is that no company holds +your money and no company holds your identity. The moment participation depends on a database of +names and email addresses, we have quietly rebuilt the thing we told people we were replacing. + +That is a fair reading and we changed the build because of it. + +## What we were actually trying to solve + +There is a real problem underneath. When something needs a member's attention, we have no way to +tell them. If a payout routes past someone because they were not qualified, or a leader needs to +reach their line about something time-sensitive, the only channel is hoping that person happens to +open their page that day. Most do not. That is not a hypothetical, it is why this came up. + +So there were two things on the table that looked like one thing. Reaching people is a genuine +need. Requiring identity is not the way to meet it. + +## What we built instead + +**Nothing is required. Anywhere.** + +Holding a position, getting paid, reading your org, the training, the promo tools, the whole +dashboard, all of it works exactly as it does today with no username and no email. Nothing on chain +touches any of this. A member who never gives us a thing loses nothing and is blocked from nothing. + +**There is no onboarding gate.** A new member registers, lands on their page, and is never stopped +by a pop-up asking for details. This is written into the automated tests as a hard rule, so it +cannot creep back in later by accident. + +**The dashboard makes an offer, once, and takes no for an answer.** There is a small card that says +you can get an email the moment a payout lands in your wallet. It has a "Not now" button that +removes it for a week. The card says in plain language that everything works the same without it. + +**One place asks, and only because it has to.** The inbox explains that messages can reach you there +only, and that adding an email means they also reach you off the site. That is not a rule, it is a +description of reality. A message cannot be delivered to someone who has left no way to reach them. + +**What we do with it: nothing.** It is never shown to other members. It is never sold. A member can +change it or remove it any time from their own dashboard. + +## The part I think you will actually like + +Leaders now see one number on their own inbox: how many people in their org can be reached off the +site, out of the total. It is scoped to your own team, so it tells you nothing about anyone else's. + +That number reframes the whole thing. Coverage stops being a rule imposed from the top and becomes +a leader's own job, solved the way everything else here is solved, by talking to your two and +letting it duplicate. If a leader wants to be able to reach their team, they have to earn that by +asking. That is the decentralized answer to a centralized problem, and it is the version worth +shipping. + +## The honest trade + +We will have lower coverage than a mandatory system would. Some members will never add anything, +and when something needs them, we will not be able to tell them. That is a real cost and we are +choosing to pay it, because the alternative costs more. + +The bet is that an offer people actually want, a note the moment money hits their wallet, will do +better over time than a requirement people resent. If that bet is wrong, the fix is better +messaging, not a mandate. + +## Separately: the "you have to get to level 8" point + +You raised this too, so here is what I found when I went through the dashboard code. + +Nothing requires anyone to reach level 8, or any level. The upgrade prompt only appears when someone +already has two people under them and money is actually about to route past them, and it names one +next level, never a target at the top. The dashboard says in two places that upgrading is optional +and self-paced. + +The one thing that could reasonably be read your way is the ladder graphic. It shows all eight rungs +at once, which does look like a climb you are supposed to finish. That was a display choice, not a +rule, but the impression is fair. It now carries a line saying you stop wherever you like, there is +no level you have to reach, and the "next" marker is only a suggestion based on who is below you +today. + +So: no change to how anything pays, and a wording fix where the picture was doing the misleading. + +--- + +Nothing here has been sent to the team. If you see a problem with any of it, say so and it changes +before it ships. diff --git a/qa/live-visitor.mjs b/qa/live-visitor.mjs new file mode 100644 index 0000000..5197ffa --- /dev/null +++ b/qa/live-visitor.mjs @@ -0,0 +1,32 @@ +// Read-only smoke check against PRODUCTION: a visitor on any ID-keyed shared link +// must see no profile prompt of any kind, and no errors. Anonymous, GET only. +// Run: node qa/live-visitor.mjs +import { pathToFileURL } from 'node:url'; +const PW = 'D:/Projects/MarketingAgent/qa-tester/node_modules/playwright'; +const { chromium } = (await import(pathToFileURL(PW + '/index.js').href)).default; +const B = process.env.LIVE || 'https://rmcircle.team'; +const ok = [], bad = []; +const t = (n, c, extra) => { (c ? ok : bad).push(n + (c || !extra ? '' : ' -> ' + extra)); }; +const browser = await chromium.launch(); +const ctx = await browser.newContext({ viewport: { width: 1280, height: 950 } }); +const p = await ctx.newPage(); +const errs = [], bad401 = []; +p.on('pageerror', e => errs.push(String(e.message).slice(0, 140))); +p.on('response', r => { if (r.status() === 401 && /\/profile|\/reach/.test(r.url())) bad401.push(r.url()); }); + +for (const u of ['/my/21', '/my/49', '/my/787', '/join/21', '/fast-start?id=21', '/generation-pay?id=21', '/flyers?id=21']) { + await p.goto(B + u, { waitUntil: 'domcontentloaded' }).catch(() => {}); + await p.waitForTimeout(2600); + const modal = await p.evaluate(() => !!document.querySelector('#pgCard')); + const invite = await p.evaluate(() => !!document.getElementById('pcGo')); + const txt = await p.evaluate(() => document.body.innerText); + t('visitor ' + u + ': renders, no modal, no invitation', + !modal && !invite && txt.length > 300, 'modal=' + modal + ' invite=' + invite + ' len=' + txt.length); +} +t('no 401s on profile/reach for a visitor', bad401.length === 0, bad401.join(',')); +t('no uncaught page errors', errs.length === 0, errs.slice(0, 3).join(' | ')); + +console.log('LIVE PASS ' + ok.length); +for (const b of bad) console.log('LIVE FAIL ' + b); +await browser.close(); +process.exit(bad.length ? 1 : 0);