Two sign-in failures with different causes and no way to tell them apart from
the outside, so stop guessing and get the data off the member's device.
/wallet-check reports what the page can actually see: whether window.ethereum
exists, which wallet flags are set, which EIP-6963 wallets announced, the
accounts and chain the wallet returns — and critically, any CSP violation.
Our script-src is 'self' with no 'unsafe-inline', and a wallet browser that
injects its provider via a script tag would be blocked silently, producing
symptoms identical to "no wallet installed". The company dApp sends no CSP at
all, which is a plausible reason it connects where we do not. This page will
confirm or kill that theory rather than us theorising further.
"Signature does not match this wallet" was true but useless — it never said
WHICH wallet signed. It now names both the address the page asked for and the
address that actually signed, which identifies the classic multi-account case
(wallet signs with the selected account, not the one the page picked up) in
one glance.
Verified for the case in hand: 0x20E0…2248 IS the wallet registered to
position #52 on-chain, so ownership was never the problem.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Marty spotted that a member's page played the angle video and then its button
sent readers to /join/<id>?v=<angle> — which is a SQUEEZE step that shows
that same video again before anything else.
His instinct was to jump straight to /join-now. That fixes the replay but
skips the proof layer: the live payout feed, the 2-4-8-16 explainer, the
toolkit line and the sponsor card all live on the full invite page. Going
from a personal story directly to "connect your wallet and send POL" is a big
jump for somebody who arrived cold from a Traffic Desk banner.
So the button now drops the ?v= and lands on the full invite page. The
existing design already gives exactly the wanted behaviour — the squeeze step
is skipped, the proof is kept, and no new parameter was needed.
The QR deliberately keeps the angle: a scanned code often reaches someone who
never saw the page, off a printout, and the hook video is what they need.
Also: payout alerts now name what an upgrade unlocked, on upgrade pass-ups
only. Marty noticed the toolkit was never mentioned — the unlock line was
wired to `upgraded` events, which are rare next to payouts. On a pass-up the
buyer below just climbed, so saying what they unlocked makes the reason for
the payment concrete. Entry rewards stay silent: nobody changed level.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two things needed before testers get level overrides.
1. The override decided what a position could SPEND as well as what it could
SEE. A tester at Scintilla overridden to Corona would have had 150,000 real
network impressions instead of 2,500, and a 200,000-impression grant pool to
hand to real members — real inventory, spent for real. Ad allowances and
split-test launches are now metered on trueLevel, and grant pools are capped
to small preview amounts (5,000 impressions / 25 copy / 2 pages / 5 ad
batches) when a position is overridden: enough to exercise the whole flow,
trivial to lose.
2. suite-traffic.stop() read the ledger, made TWO network round trips to the ad
network, then wrote back the object it had read seconds earlier — silently
discarding any campaign another member launched in that window, with the
impressions already spent on the network. It now re-reads after the awaits
and mutates the fresh copy.
Worth recording that this was the ONLY such race: Node's single thread makes a
synchronous read-modify-write atomic, so the meter, grants and split ledgers
were never at risk. The danger was only ever the await gap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The player was showing a blank white rectangle, which reads as broken. That
is frame zero of each recording — the instant before the page painted. Only
frame zero is white (by 0.5s the real UI is up), but frame zero is exactly
what a browser picks when no poster is set.
Each video now has a poster pulled from ~30% in, where the tool is in a
meaningful state rather than mid-intro. Posters live beside the videos in the
volume and the player derives the URL from the video's own, so adding a new
walkthrough needs no page change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The route matched GET only, so a HEAD probe fell through to the JSON
handler and 404'd. Some players issue HEAD before streaming; they would
have concluded the file did not exist.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Twelve narrated walkthroughs, one per Suite tool, in Marty's cloned voice.
No screen recording involved: Playwright drives and records the real UI,
ElevenLabs narrates, ffmpeg muxes. Pipeline lives outside this repo at
../video-pipeline.
Placement (Marty's call): TOP of each tool page but COLLAPSED — a one-line
"Watch the walkthrough" bar that auto-opens on a member's first visit to that
specific tool and stays shut after. A video in a separate library never gets
watched; an embedded player pushes the tool below the fold on mobile for every
returning member. First-visit autoplay is muted, so it is never a surprise
noise, and every localStorage access is guarded because it throws outright in
private windows.
Served from the data volume via /tv/<slug>.mp4, NOT from public/. That
directory is already 199MB and another 33MB of MP4 would ride along on every
deploy forever; this way a video can also be re-cut and dropped in without a
rebuild. The route implements HTTP range requests — without them the scrub bar
renders but cannot seek, which looks broken in a way people rarely report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Marty's read was right — the upper tiers felt flat, and the reason is that
they scaled QUANTITY, not KIND. L1->L2 was a category change (you couldn't
write, now you can). Everything above L4 was a multiplier on a capability
already owned at L2: sound more like you, measure it, more pages, five at
once. Meanwhile the price doubles each rung — Corona costs ~17x Culmen. No
batch button is worth 17x a voice profile.
The fix is a change of axis. Every tool from L1 to L8 was a "me" tool, but
past the first levels this business isn't about you: get two, help your two,
teach them to teach. So the top now operates on the ORGANISATION.
L7 Team Grants — hand Suite capacity down to anyone in your own org.
Deliberately from a separate pool that the level grants, NOT out of the
leader's own allowance: making someone choose between equipping their team
and using their own tools means nobody ever grants anything. Recipients are
verified in-org against the contract. Grants can lift a tool the recipient's
level hasn't unlocked — that's the point, not a loophole. Being helped is
made visible to the recipient, since half the value is knowing an upline
backed you.
L8 Network Intelligence — every team ad pooled and anonymised: which angles,
headlines, creative and formats actually earn their impressions. No member
alone has enough traffic to learn anything from display; together they do.
It can't be bought, and it compounds monthly with no further work. Holds two
lines: nothing identifies anyone, and nothing under 2,000 impressions gets a
reported rate — under-evidenced rows are counted and disclosed rather than
silently dropped. The written readout is hand-authored, not generated:
these are conclusions about money, and a model paraphrasing a table is
exactly where an invented number slips in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Completes the ladder — no tier is a placeholder now.
Every tier below builds one thing at a time, which is right for someone
still finding their words. At the top the constraint is different: these are
people running organisations who don't have an afternoon to spend clicking.
So one pass produces five social posts across five different angles, three
outreach messages (first approach, follow-up, pyramid-scheme answer), an
email, and three text ads — all carrying their link, all in the member's own
voice profile if they have one.
Partial failures are reported rather than hidden: if the engine can't finish
a section the pack says which, instead of quietly handing over a short week.
API access is real, not a label: a personal key over
GET /me, GET /team, POST /generate. Keys are stored hashed and shown exactly
once, compared in constant time, and every call goes through the same meters
and compliance rules as the web tools.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
suiteLevelOverride ('id:level,...') raises ONLY what the tool wall unlocks,
so the owner can walk every tier without buying upgrades to QA his own
product. Nothing financial moves: payouts, contract reads and every number
shown to a member still come from the real level, kept on trueLevel. Clones
rather than mutates, since the member object is a shared cache entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dispatcher only forwarded the bare /p/<id> shape, so Funnel Factory
pages fell through to the static handler and 404'd instead of rendering
(or falling back to the member's main page).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four new tools, so no tier is a placeholder any more:
L4 Voice Profile (/suite/voice) — five short answers that ride along with
every Copy Engine and Email Engine generation. It sits BEFORE the compliance
block in the prompt on purpose: a personal voice must never be able to talk
the model out of the honesty rules. suite-email builds its own prompt, so the
profile is threaded in there separately or email would keep sounding generic
while the Copy Engine sounded like the member.
L5 Split Tester (/suite/split) — 2-3 variants launched as one test with the
impressions split evenly, then click counters compared. Deliberately
conservative: it will not declare a winner until both arms have real volume
AND the leader is clearly ahead, because a 3-click gap on 400 impressions is
noise. A genuine tie is reported as a tie, which is useful information too.
A failed arm rolls back the whole test rather than leaving half of it running.
Apex is the right home for it — that is where impressions jump to 50,000.
L6 Funnel Factory (/suite/funnel) — extra named pages at /p/<id>/<slug>, so a
leader can run one page per audience and point different ads at each. Missing
slugs fall back to the member's main page, same no-dead-ends rule as /p/<id>.
L7 Leader Ops (/suite/leader) — the coaching radar already existed in chain.js
and only admin could see it, which was backwards: the leaders running those
legs need it more than anyone. Now scoped to the member's own organisation,
with a written plan built on contract-read facts only. Follows the
teach-forward rule — the digest gives the leader words to hand down, not just
numbers to act on.
Tile copy across the wall and the payout alerts now describes what actually
exists rather than what was sketched. suite-tools.js was stale in both
directions (it still had the Traffic Desk at L5 and L3 not live).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Text placements out-perform banners on this network — 357 text ads average
~171 clicks each against ~150 for banners, on far less inventory — so
members can now run them too.
The format is measured, not guessed. Across 60 live text ads: Subject caps
at 20 characters and Body is ALWAYS exactly three <br>-joined lines of at
most 24 characters. Every ad in the sample obeyed it.
Emoji: the NAS columns are latin1, but so is the connection, so UTF-8 bytes
pass through and render — which is how the existing emoji ads got there. The
bridge's clean() would have deleted every emoji via iconv //TRANSLIT//IGNORE,
so text copy now uses clean_ad_text(), which preserves them and strips angle
brackets instead. The bridge builds the <br> separators itself from a lines[]
array, so a member can never inject markup.
The engine writes plain text to a tight budget and we apply emoji ourselves:
models cannot count characters, least of all with emoji, so decoration is
arithmetic we control. It also lets the emoji rotate independently of the
copy. The palette carries no money emoji — the network is full of them, but
implying earnings outranks matching the neighbours.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three fixes from live testing of the Traffic Desk:
- /p/<id> with no page built now 302s to /join/<id> instead of returning
raw JSON 404. Any /p/ link already on a banner, flyer, or in a DM must
always land somewhere useful.
- The personal-page ad destination is only offered (client) and only
accepted (server) once a page actually exists; otherwise the member is
pointed at the Page Builder.
- Members can stop a running banner and get the unserved impressions back
in their monthly balance. Counters are read before deactivation, since
deactivating zeroes `remaining` and would look fully served.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both verified against contract source today (payout loop: level > levelIndex && 2 directs; one structural skip per depth).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified against the exact-match ABI on Sourcify: per-level entry rows on company charts are vestigial contract data with no callable path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live getAllCosts() numbers: entry 360.58 incl 5% (sponsor 326.24), upgrades double exactly per level, 100% member-to-member. Answers the 'why is entry different from upgrade / is the chart inflated' question with verifiable numbers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Marty: the tear-off designs did not look good next to the artwork handouts. Page renamed Printable Handouts; dashboard link, tools card/nav, chatbot answer, and AI prompt synced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New shared wallet-notice.js on join-now, my, training: if config.walletNotice
is set, shows an admin-authored heads-up right above every connect/unlock
button; empty string hides it everywhere (one PATCH, no deploy). Placed at the
friction point only, not a sitewide banner, so unaffected wallets/Mini App
users are not told about a warning they will never see.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaced every retire-your-link instruction (8 spots across index, join,
start, my, training FAQ, chat.js core-strategy + qualified + moving-link
answers, and the AI prompt) with the automated doctrine: after 2/2 the same
invite link self-routes each signup to the next position in the leg that
needs a direct; personal effort shifts to coaching your two. Raw-ID dApp
signups on a qualified position stay documented as the bonus path. Video
content already correct (Video 1 rebuild script fixed this framing before
recording; Method lesson scripts never said retire).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New card in the tools objection section: truth (name the product first;
business-in-a-box inventory; wallet-is-the-login members area) + the
ready-to-send casual reply (Lesson 1 free preview, verify-before-you-join
close). AI prompt tools inventory notes the objection bank.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The product now has a lid. Server refuses the gated video FILES without a
wallet-verified member session (the Mini App bridge session counts, so
Telegram members see no locks). Lesson 1, every poster, and all join-funnel
and transparency videos stay public: free preview + trust engine. Training
page renders locked cards with a one-signature unlock (same SIWE flow as
Messages) and a members-unlocked badge. Chatbot: new members-area canned
answer, Method answer notes the preview, AI prompt routing rule now answers
prospects directly instead of sending locked links; bot lesson command notes
the unlock path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
friend-circle-290a3815db.mp4: Marty-only share URL, referenced from no page.
recruitMsg + its Get Started button now read config.recruitCtaUrl (new PATCH
key, default home page) so notification CTAs can point at a proof-first
landing like /my/21. Tweet CTA was already config-driven (value PATCHed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each coach card now carries a pre-written, situation-specific teach-forward
message (all four recommendation kinds) sent over the existing wallet-verified
Messages rails: msg-send plus the Telegram bridge. When not signed in, an
inline prompt appears and the page smooth-scrolls to the Messages card.
Chatbot canned answer and AI prompt synced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nav-dash.js on all header pages: a top-right My Dashboard button when the
visitor is known (msg session from wallet sign-in or the Mini App bridge is
authoritative; else the last dashboard opened on the device). Prospects with
no identity never see it.
recordEvent was silently dropping the new engaged + ctb-offer-* events
(whitelist); now recorded per source, with admin funnel tiles for both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>