Commit Graph

310 Commits

Author SHA1 Message Date
martbost 2f90e1a97f QA pass on the member profile gate: stable button ids, completion opens the dashboard tab, local devCode, 47-test E2E suite
Findings fixed: the gate's buttons had no stable ids (fragile to test and maintain), and finishing the
gate left the member on the pitch tab where the profile card and Messages are not visible, so
completion now opens the Position Dashboard tab. /api/public/profile/email-start returns devCode
outside production so the flow is testable locally, matching the InstantAdPay pattern.

qa/profiles-unit.mjs (28 assertions) and qa/gate-e2e.mjs (47 assertions, real sessions, real UI) with
a README. All pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:40:03 -05:00
martbost 2c5fff20f5 Members can change their username and email: in-place profile card replaces the separate alert-email opt-in for signed-in owners
The first-time gate cannot be dismissed; editing later can. The 'Email me my alerts' card becomes
'Your member profile' for a signed-in owner, showing the username and confirmed email with Change
buttons, so the verified address is the one source of truth for both messaging and payout alerts.
Visitors and non-owners keep the original opt-in form unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:28:26 -05:00
martbost 1ea8f8df48 Profile gate fires only on the member's own dashboard, not when they browse a teammate's page
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:18:15 -05:00
martbost dd74ded287 Profile read endpoint answers 200 signedIn:false for visitors, so a shared /my link logs no console error
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:05:05 -05:00
martbost 7d07fc01f1 Required member profile: username + verified email per position, gated at proof of ownership
Marty, 2026-09-16: leaders can write but 94% of positions cannot receive (47 of 771 have ever signed
in to messaging, 482 messages sit 85% unread). profiles.js stores username + verified email per
POSITION (one wallet holds one position, so a Triple Play holder has three; the person is the email
and one email may hold several positions). Seeds the 40 emails already on file from
member-alerts.json, pre-filled but unverified so confirming costs one tap.

Writes are only ever accepted from a session that PROVED ownership: wallet personal_sign
(messages.verifyChallenge) or the Telegram Mini App bridge. The public /my/<id> page is untouched and
cannot write a profile, verified by test: all four endpoints 401 unauthenticated while /my/21 stays
200. Endpoints GET /api/public/profile, POST .../username, .../email-start, .../email-verify, plus
GET /api/admin/profiles for coverage. Email codes: 6 digits, 15 min, 60s cooldown, 5/day, 6 tries.

profile-gate.js is a two-step modal that cannot be dismissed, fired on dashboard boot (covers the
Mini App landing) and right after a wallet sign-in. Chatbot canned answer + AI prompt updated.
28 unit tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:56:35 -05:00
martbost 8521f7a3f2 Triple Play: highlight the Corona catch (Gen 8 Corona buys pay your Gen 1, which is you) on how-pay-works, tools objection bank, chatbot canned + AI prompt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:46:11 -05:00
martbost 8da1c144ea Marty is a team leader, not a program founder: fix the #21 upgrade-tip wording on how-pay-works and in the chatbot
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:44:11 -05:00
martbost c89bbb5c59 how-pay-works: keep level and amount cells on one line in the level-scale table
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:43:41 -05:00
martbost fedbb7f0f7 how-pay-works: depth-ladder diagram shows all 7 paying layers; Premium/Standard table lists all 7 upgrades; level-scale table adds a green net-after-upgrade-price column
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:40:29 -05:00
martbost c371149507 Chatbot: canned + AI-prompt answer for reading the generation table (level you hold vs level they buy; Vertex row is gen 7, not a total)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:35:57 -05:00
martbost 19127a11a1 how-pay-works: generation table shows the level you hold AND the level each generation buys (Clinton's off-by-one reading), plus running-total note
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:34:42 -05:00
martbost cbfc642187 Upgrade promo overlay: flyer shown once per session on the dashboard and Mini App (announce.js promo mode, admin-configurable promo* keys) 2026-09-16 04:22:53 -05:00
martbost 59db1bd711 Generation Pay: consistent row labels (positions moved to the header), no wrap on phones 2026-09-15 13:39:36 -05:00
martbost a3e08c3435 Event pop-up: Telegram app training flyer (Tue Sept 15)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 10:20:12 -05:00
martbost 3bed091ba2 Banners: Buffett 'bucket, not a thimble' quote graphics (landscape + square)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 05:37:21 -05:00
martbost 13eabc6b9a Payment proof card points to the public RM Circle Proof channel (t.me/rmcircleproof)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 05:11:25 -05:00
martbost 996c5b2a69 Show the Telegram payment channel in the live payment proof section (home + join)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 04:57:13 -05:00
martbost 344f99067d Banner: The Perfect Moment (Polygon payments in flight)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 05:05:34 -05:00
martbost 359bfee3db Event pop-up: Add-to-calendar buttons (Google Calendar link + .ics for Apple/Outlook)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:02:07 -05:00
martbost a01526e888 Add Sept 10 Weekly Huddle flyer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 16:57:43 -05:00
martbost 860efc4912 Matrix view: zoom controls so small screens can see the whole pyramid
The team pyramid overflows on phones. Add -/+/Fit zoom controls to the matrix
(pyramid) view that scale it via CSS zoom, defaulting to auto-fit so the whole
structure fits the screen width on load. Controls show only in pyramid mode,
re-fit on resize and when the dashboard tab opens, and guard against measuring
while the tab is hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 19:33:11 -05:00
martbost 946a9d75b5 Gate consistency video to members; embed on rhythm page; add vertical cut
- Members-only: the stay-consistent video now requires a wallet-verified session
  (server 403 + training-page lock UI), matching Module 3.
- Embedded on /weekly-rhythm above the sheet (hidden in print) with a members-only
  fallback card for signed-out visitors.
- Added a ~31s vertical (1080x1920) social cut, ungated, for Telegram/social.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 06:08:46 -05:00
martbost 47955c6b56 Add "Consistency Wins the Race" training video
A narrated, animated companion to Circle Method Lesson 10: inspires members to
keep the weekly 20-minute rhythm, print and fill the Rhythm sheet each week, and
duplicate-and-teach rather than reinvent. Placed after Lesson 10 in Module 3,
with a watch link added to the printable /weekly-rhythm page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 05:46:58 -05:00
martbost e3f6b00f95 Make event pop-up admin-toggleable; drop it from join pages
The Weekly Huddle overlay now reads its content from /api/announce (config-
driven) instead of a baked-in block, and Admin -> Settings -> Event pop-up
exposes a toggle plus editable fields (eyebrow, flyer, join URL, date, times,
auto-hide, event id). Defaults are seeded into config on boot so the panel
reflects real state. Removed the include from join.html so it never interrupts
the join flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 05:28:03 -05:00
martbost be7cb93bc5 Add dismissible Weekly Huddle pop-up overlay (Sept 8)
Self-contained /announce.js injects a branded, dismissible event overlay
(the flyer + all four time zones + Join Google Meet button). Included on the
main funnel and member pages. Auto-expires after the huddle and shows once
per browser until dismissed; edit the EV block to reuse for future events.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 15:42:56 -05:00
martbost 44651a92b7 Textbook Play copy: Marty is a top team builder, not a program founder
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 15:11:33 -05:00
martbost 106f0fa7bd Training: 'The Textbook Play' coaching video — a real position run by the book
111s in Marty's voice: position #21's ledger, catch-funded climbs, the
pass-over cost, this week's Apex catch-and-climb, and the dashboard/sponsor
next-play close. Chatbot + AI prompt synced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 14:54:06 -05:00
martbost dd6730e82f Training page: replay callout upgraded to a full attention banner
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 07:04:49 -05:00
martbost 2cf11441ff Replays copy: the team meets at least weekly, sometimes twice
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 06:58:42 -05:00
martbost 79470e471c Members area: weekly webinar replay page at /replays
Wallet-gated like the Method lessons: the API returns titles/dates to
everyone (prospects see the shape of the product) and video URLs only with a
member session. List lives in the data volume (replays.json) so the weekly
add never needs a deploy; videos stream from the DO Spaces bucket (CSP
media-src added). Teaser on /training; chatbot + AI prompt updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 06:46:06 -05:00
martbost 726bc24e09 Traffic Desk UI: trust the bridge's served figure — remove the count-up banner branch here too
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 06:22:16 -05:00
martbost 747977b368 Catch eligibility: catcher needs level >= the level the buyer is LEAVING, not the level being bought
Matches _payUpline (level > levelIndex, levelIndex = buyer.level-1). The old
depth+1 rule told #21 (Culmen) it could not catch #49's Apex buy; #21 is #49's
4th matrix upline and does catch it. Fixed in the owner alert, coaching scan,
dashboard pipeline, AI prompt, leader digest, chatbot and how-pay-works copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 10:51:30 -05:00
martbost dcc8202efb Angle squeeze pages: fit headline, video and CTA above the fold
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 09:40:07 -05:00
martbost 71ba633ac5 Angle squeeze pages: quieter header, legal-only footer, hold proof toasts until the hook video ends
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 09:26:25 -05:00
martbost 7bd72e7de7 Clean poster frame for the AdRevSplit explainer
Two problems with pulling the poster straight out of the recording: it caught
the walkthrough's synthetic cursor, and the hero QR was in shot. That QR
resolves to the CURRENT TEAM SPONSOR via the moving link, so anyone scanning
it off the builder entry would route around the AdRevSplit sponsor whose
{affiliateid} the entry is personalised with. Shot fresh with .qr-cta hidden.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:38:01 -05:00
martbost 26d0ff78ad Poster for the RM Circle explainer used in the AdRevSplit downline builder entry
The 88s walkthrough itself lives in the data volume at /tv/rmcircle-what-it-is.mp4
(same route as the Suite tool videos) so it doesn't ride along in the image.
Only the poster frame needs to be static.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:33:37 -05:00
martbost 5978ef139f Suite: force the wallet account picker when switching position
Clearing our session was only half the problem. eth_requestAccounts returns
whichever account the wallet already has connected to the site - it never
opens the picker - so a member holding two positions could sign out, switch
accounts in MetaMask, reconnect, and be handed the same address again. Signing
with it resolved to the same position, which looked exactly like the switch
doing nothing.

This is the trap already documented in our own Triple Play guidance, and the
Triple Play is what puts members in this situation in the first place.

Switching now calls wallet_requestPermissions first, which forces the account
selector open. Only on switch, so ordinary single-position sign-ins are
unchanged. And if the wallet still returns the same address, the page now says
so - naming the address and pointing at the wallet's connected-sites list -
instead of silently redisplaying the same position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 15:21:44 -05:00
martbost 2d1ccadfd9 Suite: let members switch between positions they hold
A member holding #2 and #3 connected #3's wallet, closed the browser, came
back, and still saw #2. Not a wallet problem: the sign-in cookie lasts 30 days
and there was no sign-out anywhere on the site, so the first position he
authenticated as was pinned to that browser and the wallet was never consulted
again.

This hits precisely the people we tell to buy several positions - the Triple
Play is on /how-pay-works and in the chatbot - so it will keep happening.

Adds POST /api/public/signout (drops the server session and expires the
cookie) and a "not this position? switch" link beside the identity line on
/suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 15:07:29 -05:00
martbost 7e390aa48e Join: let chosen positions default to direct placement
The moving link is team-first by design: it routes a new join to whoever needs
directs next, so the position whose link was actually clicked earns no entry
reward. Measured on #21 that came to 80 joins in seven days and zero entry
rewards, while the traffic paying for those joins was the owner's.

For a position being deliberately built out, that default is backwards. Any ID
listed in the new directDefaultIds config now behaves as ?direct=1 on its own
/join/<id> link - including every promo tool, flyer QR and downline-builder
entry that resolves through it - without needing the parameter appended.

?direct=0 still forces rotation for a single share, so the team-first behaviour
stays available rather than being replaced.

Config rather than hard-coded, and editable from the admin settings form, so
which positions get this can change without a deploy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 10:29:55 -05:00
martbost 34480d444e Dashboard: let members see what a nudge actually says before and after sending
A member sent a nudge to #102, then went looking for what had gone out. The
only feedback was a small "Delivered to their dashboard" note, so she checked
her clipboard - which still held the unrelated "Copy plain-English summary"
text describing her OWN position. She reasonably concluded the nudge had sent
her position instead of the recipient's.

It had not: the message log shows #34 -> #102 carrying #102's own numbers.
The feature was right and the feedback was wrong, which is its own kind of
bug - a member who cannot verify what they sent will assume the worst.

Every nudge now has a Preview button that names the recipient and shows the
exact text before sending, and on success the panel shows what was sent and
to whom rather than a bare "Delivered".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 09:42:59 -05:00
martbost 64527af2ab Publish the stagnation video
"The money came. Where did it go?" - 4:06, Marty's voice, rendered from
specs/climb-with-your-catches.json.

Covers why 95% of qualified members cannot cover their next rung despite
having earned past it: the doubling maths, pass-overs being silent, and the
contrast with members who joined days ago and are already climbing. Ends on
the self-paced / afford-to-lose line and a teach-your-two close. No income
claims, no member identified.

Shareable at /v/rmc-climb-catches-d15c3f03fa.mp4 with a poster frame
alongside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 07:08:37 -05:00
martbost 25eddf19c4 Dashboard: show what an upgrade buys, not just what it costs
The upgrade panel showed a price, a funded badge and a button. Nothing on it
said what the member would get. 53 of 87 qualified members in the org are
parked at Ascensus, and a panel that reads as a bill is part of why.

It now leads with what the rung unlocks, in the member's own numbers: the
generation it extends reach to, how many people they already have sitting
there, and what each of those pays when they climb. genCounts already existed
in getOrgShare; this just points it at the member instead of at the admin.

The line that matters most is arithmetic, not persuasion: because every rung
costs exactly twice the last, the first catch at the new level is exactly
double what the upgrade cost. That is Lesson 8's whole argument, and the
panel now makes it where the decision is actually taken rather than leaving
it in a lesson nobody opens at the right moment.

Wrapped in try/catch — if the org walk fails the panel renders as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 05:18:10 -05:00
martbost c86a583488 Chatbot: the Suite is live, stop telling the bot to hide it
The AI system prompt still carried "CURRENTLY IN PRIVATE TEAM BETA: do NOT
proactively promote it; if a member asks, say it is being finalized and will
be announced soon." That stopped being true when the Suite launched to the
org. /suite has been serving publicly, and the best-performing massifly story
to date sells precisely this — so the bot was deflecting questions about the
strongest answer we have to "what's the actual product".

The prompt now says it is live, open to every position, and should lead any
what-do-I-actually-get question.

chat.js had no Suite entry at all, so the canned side never mentioned it
either. Added one covering the product question in the words people actually
use, with the level ladder and the honest note that in-development tiles are
labelled as such.

Both sides updated together — they drift apart the moment only one is
touched, which is how this one survived the launch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:14:02 -05:00
martbost 439c5885de Admin: give the wide tables the full width
The Sponsor Queue table carries min-width:850px but the 1.6fr side of the
split only handed it about 800px, so the Actions column was clipped — the
Reset button cut in half and the delete button off-screen entirely.

Sponsor Queue and Member ID Submissions now run the full width. Add Sponsor
moves below the queue as a single collapsible row of fields instead of a
narrow two-column form in a sidebar, which is both less tall and no longer
squeezing the table next to it.

The rule this follows: a card holding a .table gets the whole width; only
compact forms share a row. Settings keeps its split — those are all short
forms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 06:23:50 -05:00
martbost 7452490a78 Admin: group the fourteen panels into tabs
The page was a fixed 1.5fr/.75fr split — ten tall panels on the left, four
short config forms on the right. The right column ran out about a quarter of
the way down, so the rest of the page scrolled past an empty gutter, and
finding anything meant scrolling through everything.

Five tabs now, each using the full width: Team (queue, add sponsor, coaching
radar, award capacity, messages), Traffic (conversions, org vs network),
Members (submissions, lookup, matrix), My Money (positions income), Settings
(AI chat, SendGrid, public page).

The panels themselves are untouched — same markup, same ids, same JS. Only
their grouping changed. Panes are moved in the HTML rather than by script, so
the layout does not depend on JS running; the tab file only toggles which
pane is visible and remembers the last one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 06:16:05 -05:00
martbost ee87408dec Admin: move the bonus panel inside the admin view and collapse it
It was sitting between the login panel and #adminView — outside the admin
view entirely, so it rendered above the page header and showed on the login
screen to anyone who loaded /admin.

Now it sits after the Coaching Radar, which is the panel that tells you who
needs a nudge; this is the lever for nudging them. Collapsed by default and
the log is capped at a scrollable 240px, because awarding is occasional and
the log grows every month.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 06:11:41 -05:00
martbost 77dcda6a1a Admin: move the bonus panel script out of the page (CSP)
The site sends script-src 'self'. The panel's logic was an inline <script>
block, so the browser refused to run it: window.agBoot was never defined,
admin.js's guarded call did nothing, and the "What" select stayed empty with
no console-visible cause beyond the CSP report.

admin.html had no inline scripts before this panel — the extraction restores
that, so the next person adding a panel copies a pattern that works.

Fourth thing this CSP has silently broken. Inline scripts are not an option
on this site; new panels ship as files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 06:00:37 -05:00
martbost e4b7d64605 Admin: load the bonus panel after login, not before
The panel fetched its tool list on DOMContentLoaded — which fires while the
admin page is still showing the login form. The request 401'd, the handler
returned silently, and the "What" select was left permanently empty with no
path back.

Loads from the same post-login block as the coaching radar and messages
panels now, plus a refetch if the select is opened while still empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 05:51:26 -05:00
martbost 05afe5c5f6 Admin: award Suite capacity to any member as a team bonus
Marty asked for a way to grant impressions to anybody, labelled as an admin
bonus. Team Grants already did this for leaders, but only within their own
organisation and only out of a level pool.

adminGrant() is deliberately a different thing: it comes from no pool (the
team is not a position and has no allowance), it can reach ANY registered
position rather than only someone downline, and it carries a reason that
survives in the ledger. Recorded with by:0 so nothing downstream mistakes it
for a leader's grant — the member's own page renders those as "the team"
rather than "#0".

The route verifies the position exists on-chain before crediting it; a typo
would otherwise sit in the ledger crediting nobody, and it would be invisible
until someone went looking.

Awards land on top of whatever the level already allows and expire with the
month like every other allowance. The admin page lists what has been awarded
this month, so bonuses stay auditable rather than vanishing once given.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 05:40:00 -05:00
martbost 59bd4bd4ab Show recorded figures for closed campaigns, not the wiped counter
My own sweep created this. Deactivating an ad ZEROES `remaining` on the
network, and for banners — where that column counts UP — zero reads as
"served nothing". So every campaign the sweep closed flipped to 0 served /
2,500 left, despite the ledger correctly recording served=2500,
completed=true.

Closed campaigns now read from what we recorded at the time rather than
re-deriving from a counter that no longer holds anything. Live campaigns
still compute from the network, per placement type.

Also removed the em-dash placeholder that blanked LEFT for stopped rows —
there is a real number to show — and made stop()'s error path assume fully
served rather than fully unserved, since guessing high would refund
impressions that had actually been delivered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 05:33:55 -05:00