Commit Graph

382 Commits

Author SHA1 Message Date
martbost 91a6893df9 Member profiles are optional: an invitation on the dashboard, never a gate
Manson's objection was that requiring a username and a verified email pulls the
build back toward a centralized database of members. He is right, and the
communication gap is real too, so the answer is to ask well rather than to force.

Nothing about holding a position, getting paid, reading the org, the training or
the tools depends on contact details any more. There is no onboarding gate: a
brand-new member registers, lands on their page and is never stopped by a modal.
The dashboard offers a dismissable card ("Not now" snoozes it for a week) that
leads with the thing members actually want, a note the moment a payout lands in
their wallet, and says outright that everything works the same without it. The
inbox is the one place that asks, because a message cannot be delivered to
someone who left no way to reach them, and even there it is an invitation.

The card sits above the tab strip rather than inside the dashboard pane: the page
opens on the pitch tab, so an invitation parked in the dashboard would never be
seen by the new members it is aimed at.

For leaders, /api/public/reach answers "how many of my org can I reach off the
site", scoped by chain.isInTeam so it leaks nothing upward or sideways. That
makes coverage a leader's own problem to solve by asking, not a rule imposed on
members.

Fixes a real bug found by the rewritten suite: the dismissable flag double-booked
as "single-field edit", so saving a username in the opt-in flow closed the dialog
instead of advancing to the email step. Split into oneShot; the suite now asserts
the advance as a regression.

QA, all green: profiles-unit 28, signin-fallback 7, gate-e2e 33 (rewritten to
assert the opposite of what it used to: no forced modal, dismissable everywhere,
visitors unaffected), join-flow 12 cold / 11 refuse / 12 warm.

qa/reseed.sh carries two hard-won guards: never name a shell variable TMP on
Windows (it inherits the system temp dir and rm -rf wipes it), and never pkill.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:09 -05:00
martbost 0189278aa5 QA: end-to-end join-flow harness proving the post-registration sign-in, including the refuse regression
qa/harness-server.js boots the real server with chain reads stubbed (COLD=1 reproduces the cached-index
state that left #787 without a profile). qa/join-flow-e2e.mjs drives the real /join-now page with a fake
wallet producing genuine secp256k1 signatures.

Three scenarios pass: signs on a cold index (9) - the gate appears on their own dashboard; refuses to
sign (10) - REGRESSION, the join still completes and redirects with the page usable; signs on a warm
index (9). Full set green: profiles-unit 28, signin-fallback 7, gate-e2e 47, join-flow 9/10/9, plus the
live shared-link check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 19:05:04 -05:00
martbost 49a0778a53 Post-join sign-in actually works: live position lookup for a cold index, ordered calls, redirect no longer races the signature
#787 registered at 18:35 CT, 22 minutes after the first join-flow fix, and still had no profile. Two
causes, both fixed:

1. messages.verifyChallenge resolved the wallet through chain.memberIdByAccount, which reads the
   CACHED index. Seconds after a registration that wallet is not in it, so the signature was rejected
   with "No RM Circle position is registered to this wallet". It now accepts an idHint (the position
   id from the member's own registration receipt) and, on a cache miss, reads that id live from the
   contract via chain.verifyMember, minting only when the contract says this exact wallet owns it.
   That is a stronger proof than the cache, not a weaker one. Now async; the single call site awaits.

2. join-now.js fired the sign-in and a 4.5s redirect in parallel, so the page could navigate away
   while the wallet was still showing the signature prompt, and it did not wait for submit-id (which
   runs the live verifyMember server-side that seeds the index). It now awaits the report, passes the
   receipt id, and redirects only once the signature settles, with a 120s bailout.

qa/signin-fallback.mjs (7 assertions) proves the cold-index path with real secp256k1 signatures and
covers the abuse cases: a hint for a position the wallet does not own is refused, and a signature from
another wallet is refused. Existing suites still pass: profiles-unit 28, gate-e2e 47.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 18:49:33 -05:00
martbost 6f1c9ff8ca New members are signed in to their inbox right after registering, so the profile gate actually fires
Found by checking the live data: 59 positions registered in the 36h to 2026-09-16 and not one has a
profile row, including #785 which registered 20 minutes after the gate deployed. The join flow
redirects to /my/<newId> but the member arrives with no inbox session, so the gate never runs and the
leader still cannot reach them. join-now.js now takes the one free personal_sign immediately after the
registration confirms, while the wallet is still connected, which is the cheapest moment to ask.
Best-effort: declining does not block the join, and they are asked again on their next sign-in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 18:11:12 -05:00
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 4b982dffe0 Team-build topic: telegramTeamEvents=none leaves only the daily snapshot
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 07:50:20 -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 5cbd47db27 Team-build topic: config switch to drop payout lines (telegramTeamEvents=no-payouts)
Payout notifications migrate to the shared payments topic; registrations,
upgrades and the daily snapshot keep posting to the team-build topic.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 05:28:26 -05:00
martbost 876b6eebd8 Echo payout proof lines into a shared cross-program Telegram topic
New config keys telegramEchoTopicId / telegramEchoChatId / telegramEchoEvents
(default: payouts). Each echoed line is prefixed with the program name because
InstantAdPay posts into the same topic.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 18:50:36 -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 b9c079fa3e Route /announce.ics through the API handler
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:04:41 -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 6c2306966d Proof channel: one compact line per event (payouts + upgrades), HTML link, no hashtags
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 09:46:56 -05:00
martbost 617e519a3b Proof channel posts link to the team site, not a personal link
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 08:54:50 -05:00
martbost 42a6caacb0 Telegram: optional company payment-proof channel feed (same bot, separate chat)
config.telegramProofChatId (+ optional telegramProofTopicId, telegramProofEvents
'payouts'|'all') mirrors payout events with the recruiting copy plus the
Polygonscan receipt. Inert until the chat id is set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 07:03:23 -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 8ea371dd09 Telegram team posts: disable link previews on payout/team-build notifications
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 05:12:40 -05:00
martbost bf5e8bdee9 Traffic Desk: NAS remaining counts down for banners too — trust the bridge's served field
The count-up theory made every fresh banner read as fully delivered, so the
sweeper deactivated brand-new campaigns minutes after launch and stamped them
complete (caught by Marty 2026-09-01 on ad 2713).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 04:59:47 -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