Commit Graph

282 Commits

Author SHA1 Message Date
martbost f0a6a93f8b Campaigns: archive a finished one, get the credits back, keep the numbers
A member asked for a delete button. Deleting would orphan the credit ledger,
the hourly view rows and the P&L, and throw away the figures they actually
want, so a campaign is archived instead: out of the working table, listed in
full underneath with its views, clicks and spend, and restorable.

The credits are the real point. Reserved budget is computed from campaigns
that are active or paused, so a paused campaign quietly holds credits the
member cannot spend anywhere else. Archiving releases them the moment the
status changes, with nothing to refund and nothing that can double-refund.

Only a paused or finished campaign can be archived, so pause stays the
deliberate first step, and restoring brings it back paused rather than live.
The syndication rails are stopped on the way in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 08:40:17 -05:00
martbost 891eac1626 Overview: the PolHunter return card goes first
Marty's call: someone arriving from PolHunter stays on the dashboard rather
than being bounced back, because that is where the business is. That makes the
return card their only way back, and it was sitting third, below the launch
announcement for the site they had just come from and the Friday promotion.

It is hidden for everyone who did not arrive that way, so no other member's
page changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 06:35:33 -05:00
martbost 489d832546 Sign-in: stop telling a fast returning member off for being fast
The email-code endpoint refuses a request that arrives within two seconds of
the page loading, because a script fills a form instantly and a person does
not. A member whose email is already filled in by the browser has nothing to
type, taps straight away, and gets "Give the page a second, then tap again."

The page now waits out the remainder itself and sends when the window has
passed, so the person sees the normal sending state instead of an error.
The server check is untouched, so anything posting at the endpoint without a
page behind it still fails exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 05:50:34 -05:00
martbost 286ac9207a Hand the sponsor to PolHunter, and put its payouts in the P&L
PolHunter now pays a bounty when a hunter brings someone who turns up and
hunts, and the person who gets it has to be the one that member actually
joined under here. The sign-in payload carries sponsorRef and the join date
across so PolHunter is never guessing from a cookie.

Its payouts are real POL from its own faucet and none of it passes through the
ad contract, so the P&L could not derive it. It now reads the figures across,
cached and fail-soft, and shows finds, bounties and matches as their own card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 04:36:02 -05:00
martbost ce9f3e6904 Videos: reject a source that does not load, and stop stranding the viewer
Michael Camire reported the video tab locking up after the fifth watch each
day. Three separate faults behind it.

A campaign could go live with a video address that resolves nowhere, because
the form only checked the shape of the URL. One has been sitting there since
11 September pointing at the example host, serving every Tier 1 member a black
player it could never finish. The create route now fetches the address and
refuses a host that does not answer, a 404, or a page that is not a video.

The Watch tab holds the landscape videos and Shorts holds the upright ones,
but they share one daily count. A member who cleared the tab was told to come
back tomorrow while clips were still waiting one tap away. When the other
surface still has something, the done screen now says so and links to it.

When a browser refused to autoplay, the button offered "Tap to play" but was
wired to fetch a different video, so the loaded one was thrown away and the
next also would not start. On a phone the clip could never be played at all.
The button now starts what is already loaded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:54:31 -05:00
martbost c47905d89a Overview announcements: let a member put one away for the session
The PolHunter launch card, the Five Dollar Friday card and the "you came from
PolHunter" card repeat on a timer, so anyone who has read one had no way to
clear it. Each now carries a small close control in its heading: the card
stays hidden for the rest of that browser session and comes back at the next
sign-in, so a message nobody acted on is never lost for good.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 06:29:49 -05:00
martbost 9df669fa7c Sponsor pre-check alert: only the checkout call may alert (the dashboard's display-only call never does), and the alert says what it is: a heads-up before the sale, nothing paid yet
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 10:55:09 -05:00
martbost a403ce3520 Promo tools > PolHunter: the two vertical spots with download, caption carrying the member's link, share buttons and the platform guide
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 09:59:58 -05:00
martbost c6c26b8073 PolHunter in the member menu from launch morning (the Overview reveal still waits for launchAt); Promo tools > PolHunter kit: share link, posts, swipe, banners, both videos; reveal card says tap to copy
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 07:36:37 -05:00
martbost e5cbecbdb2 Viral link builder: dropdown only (the paste box only ever accepted the site's own pages, which the dropdown already lists)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 06:13:25 -05:00
martbost e2f88dad42 Viral links: any public page + ?ref=<username|code|id> sets the same last-touch sponsor cookie as /join, counts under a 'page' hook in link stats, then redirects to the clean URL; Promo tools > Viral links builder (pages list + paste any address, copy + share); blog posts show a signed-in member's own link; chatbot answer; QA walk proves redirect + cookie
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 05:54:55 -05:00
martbost 6f5b5dbae6 Earning guide: adds the POL side (50/20/10 split, payouts on, qualification at 2 and 5, pass-ups, sponsor lock, where to see it); title and meta cover both kinds of earning
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 04:27:27 -05:00
martbost 91456ae0ce Promo tools: Five Dollar Friday banner set (1200x630, 1080x1080, 728x90, 300x250)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 14:06:16 -05:00
martbost 68b08ab8cc Five Dollar Friday: +20% purchased-grade credits on any $5+ package bought on a Friday (Central), live wave on the landing strip and Overview card, Telegram every ten packs + Friday kickoff + midnight wrap-up, Thursday notice + email, Five Fridays badge, promo posts + swipe, chatbot; first Friday 2026-09-25
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 13:59:15 -05:00
martbost 9fafd0bf43 Training: feature 'polhunter' entries appear at the launch moment; launch card links the feeder video
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 05:27:10 -05:00
martbost 9a531538ed Launch-day surprise: PolHunter card on the Overview and the menu entry appear at siteConfig.launchAt (self-flipping, nothing visible before)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 05:16:30 -05:00
martbost 7ea7a5f409 PolHunter return loop: ?from=polhunter sets iap.return (landing + join links), /api/me carries returnTo, Overview card sends them back once a wallet is linked, wallet link answers next=/api/my/polhunter, hand-off clears the cookie
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 20:00:55 -05:00
martbost ad539d5fae Ledger: a NAS zone under the payout feed (mission pages must carry ad inventory)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 16:40:07 -05:00
martbost 5fe3dfa625 Dashboard: PolHunter menu entry removed until it opens (the hand-off route and the ledger embed stay; neither is visible)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 16:15:26 -05:00
martbost af710f68b8 PolHunter hand-off, nav entry, and the ledger embed
/api/my/polhunter signs a member across to polhunter.com with a five-minute single-use HMAC
token (memberId, email, wallet, username); PolHunter has no sign-up and no mailer, this is the
only way in. A PolHunter entry in the dashboard nav. The PolHunter embed on the public ledger
with one slot beside the payout feed; CSP allows polhunter.com.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 14:20:16 -05:00
martbost a85ee4cacf Login slots: 1,000 credits, at most 3 member slots live; viewer gets an open-in-tab link
Marty (2026-09-19): cap the login-ad slots. About 124 members sign in a day and every live
slot shares those views, so a fourth slot only thins the other three. Member slots only;
house login ads now step aside whenever a member slot is running (JSON: filtered out of the
pool; MySQL: ORDER BY house ASC). The 14 slots already running keep their term; new buys are
refused until one ends. Format label and chatbot updated.

mindfulmojo (email, 2026-09-19): the viewer frames the advertiser's page and both controls
meant "back"; some sites refuse to load inside a frame and signing up is easier in a full tab.
The viewer bar now carries "Open site in a new tab", set from the ad's target URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 07:46:12 -05:00
martbost 415a5f3848 Partner-code credits arrive as a drip; unclaimed installments expire
Marty (2026-09-19): partner codes keep working with no monthly cap, but the credits stop
being a dump. A fifth is paid the day the code is redeemed, then a fifth on each day the
member finishes their daily ad set, five days in all, and whatever is unclaimed 30 days after
redemption is never paid. Same headline number for the partner to promote; paid only to
people who show up. 49 of the 52 PARTNER redemptions were still sitting on 500+ unspent.

Every redemption now records the site the ?promo= link was opened from (iap.promoref cookie
set from the Referer at link-open), so a code's traffic can be attributed to the partner page
it was supposed to come from. Member-funded codes charge the funder one installment at a
time; a short funder leaves the installment due, not lost.

Pre-drip redemptions were paid in full up front and are marked paid=credits at start-up, so
nothing is paid twice. Covered by a module test (13 checks: per-day cadence, gaps, rounding,
expiry, pre-drip rows, declined payer) and an end-to-end HTTP test on a local copy (9 checks).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 06:24:14 -05:00
martbost 5ebe890760 Paged lists: 25 rows, then a clearly marked next page
Marty (2026-09-19): once a membership gets large, My line and the other lists scroll forever.
One helper, pageList(), pages what each pane already renders: rows past the current page are
hidden and a solid-mint control bar (row range, Previous/Next 25, page count) goes under the
list. Row markup and bindings are untouched, so it goes on every growing list at once: My
line per level (the Activity drawer pages with its row, and a chip drill-down opens on the
right page), the Overview roster, the Campaigns table and its credit ledger, each Pipeline
column, and the Messages thread list. Asset tags bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 06:02:28 -05:00
martbost 0a50d07658 Login ads become a flat 30-day slot
The daily meter charged 100 credits for every day a campaign was shown,
with no end date. It produced 74-day queues, and worse, each new campaign
added 100/day of charges while adding no inventory: on 18 September, 15
campaigns were charged 1,500 credits against a platform that had produced
367 login impressions in its entire life. That had stopped being a pricing
inefficiency and become members paying for delivery that did not exist.

Now: 3,000 credits buys 30 days of unlimited impressions, charged once at
purchase, with no delivery promise — the model Marty wanted, matching how
ClickBaitPays sells theirs. The price is deliberately identical to 30 days
at the old daily rate, so nobody pays more than before; they just get a
known end instead of an open-ended drain.

It reuses the `featured` shape, which was already a flat up-front buy that
runs to `expires`. Both daily meters — MySQL and JSON — now skip any
campaign with an expiry, so a slot can never be metered. The existing
sweep closes it on day 30 and returns anything unspent.

qa/login-slot.mjs, 9 checks, including that the slot price still equals
30 days at the old rate and that neither meter can touch a slot campaign.
Member walk clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:18:27 -05:00
martbost ccc372152d Sessions last 4 hours, and we say why
Marty's call. Sign-ins expired after 24 hours, which meant a member passed
the login gate roughly once a day — and that gate is the entire inventory
behind the login-ad product. The platform has served 356 login impressions
in its life while members have bought 20,526 credits of them.

Four hours, env-tunable via SESSION_TTL_HOURS.

Said out loud in three places rather than left to feel like a glitch:
the login gate itself now explains it, the chatbot has a canned answer for
"why do I keep getting logged out", and the same fact is in the assistant's
system prompt so the AI never calls it a bug. Wording throughout is
Marty's: to keep resources optimised we do not keep anyone signed in
longer than 4 hours, and nothing about the account, credits or line is
affected.

The canned pattern was checked against the neighbours it sits beside —
seven phrasings reach it, and withdraw / pyramid / earnings / second
account / payout answers are all untouched. CANNED is first-match-wins,
so that check matters.

Member walk clean, chatbot suite 35 green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 13:31:52 -05:00
martbost c5a846bd91 Keep the notifications worth interrupting for
Splitting system messages out of the modal was right for payout receipts,
but it would have silenced two that a member genuinely loses money by
ignoring:

  - "@someone is trying to buy. Link your wallet so it pays you" — a sale
    is blocked right now, and the referral is lost permanently once it
    routes to someone else.
  - "You missed 43 POL on InstantAdPay" — a payout passed them by, and the
    message explains exactly how to stop the next one doing the same.

So the dividing line is not system-versus-human, it is "does this need you
to do something". Those two become kind 'alert' and still interrupt; the
receipts stay kind 'notice' and stay in the inbox.

The modal no longer credits an alert to a person either. It was saying "A
message from @martbost" over machine-generated text, because system mail
is sent by member 1 at ADMIN_EMAIL. Alerts now read "Action needed on your
account".

Also reclassified "X is now in your line for good" as a notice — it is
good news about a referral the member gained, with nothing at stake.

qa/messages-notice.mjs now covers both lanes: 14 checks, including that an
alert interrupts and a flood of 25 receipts does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 08:25:08 -05:00
martbost a2c77d01d1 Stop payout notices interrupting members who are earning
Marty hit this viewing daily ads: a popup after every single return to
the dashboard, each one a different payout notice.

Two causes, both fixed.

The channel was shared. "You just got paid 40.8923 POL" and "Welcome to
my line, here are your first three moves" were stored identically, as
kind 'broadcast' — the kind the sign-in modal is meant to interrupt for.
Dismissing one just promoted the next unread notice, so a backlog became
a carousel. System messages are now kind 'notice': they land in the
inbox, count toward its badge, and never pop. Only a message a person
actually wrote can interrupt.

The modal also had no memory. loadDashboard() runs on far more than
sign-in — after every ad view, campaign edit and chat close — and it
re-popped each time. It now shows at most once per page load and never
twice for the same message.

The 79 existing machine-generated rows are retagged by a migration in
ensureSchema, 15 of them unread and currently popping. Matched on
subject rather than sender on purpose: these come from member 1 at
ADMIN_EMAIL, which is also Marty's own member address, so his genuine
broadcasts sit under the same sender and must be left alone. Verified
against the live data first — "Credits returned: a counting error on our
side" and the broken-banner note are his, and stay as broadcasts.

qa/messages-notice.mjs covers it: a flood of 25 notices produces no
interruption, the human message still does, and chat stays in its own
lane. Member walk clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 08:09:50 -05:00
martbost e95f9df13a Approved exceptions: people you have okayed to hold more than one account
Marty approves specific people for multiple accounts (partners, staff, a spouse on a
shared machine) and needed a way to say so without the guard fighting him.

Admin > Members > Duplicate signals now carries an "Approved exceptions" list: add an
email with a note, see who is on it and when, remove one. It sits directly under the
signals so the two are read together.

An exception can be added against the address they ALREADY have, not just the new one.
That matters because the usual case is approving a person before their second address
exists, and at sign-up time the new address is unknown to us. checkSignup now tracks
every account the sign-up collided with, and clears the block if either side is approved.

Clearing the HARD flags matters as much as clearing the block. Those flags are what
silently drop an account off the leaderboard and bar it from adopting out of the holding
tank, so an approved person would have been "allowed" in name only. They now keep both.
The account is tagged 'allowlisted' instead, so the admin sees why it went through, and
the server logs the exception by name.

Suspension still wins. An exception is permission to hold several accounts, not immunity
from being suspended for something else.

qa/fraud-allow.mjs (12 assertions) boots its own server and walks the real flow: first
account created, second blocked with the Qualified Start redirect, exception added,
second account now created, no hard flag left on it, exception visible in the admin
report, removing it blocks again, malformed address refused, endpoint admin-only.
qa/sponsor-note.mjs 5, qa/run.sh member 0 bugs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 10:42:31 -05:00
martbost 8ad2e01a74 Admin: a linked wallet can no longer be misread as an active position
The member card showed the wallet address on its own, directly above "Registered:
no (payouts off)". That reads as "he is set up" when he is not, and it caused a
real misread today on @mcbit1: wallet linked, memberId 0 on chain, every sale in
his line walking up to his sponsor while the row looked healthy.

Linking a wallet is a free signature that tells the site which address is theirs.
Switching on payouts is a separate transaction that creates the position. Only the
second one makes them payable, so the two states now say so:

  Main wallet  0x30a7…5703  payouts OFF
  Registered   no  wallet linked, but payouts were never switched on, so no
               position exists. Sales in their line walk up to their sponsor and
               lock there.

and the no-wallet case says "no wallet linked yet" instead of the same text.

qa/run.sh member: 0 bugs (the 2 warnings are pre-existing and unrelated).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:03:29 -05:00
martbost 34c62b9d3a Anti-fraud: one account per person enforced at sign-up (device cookie + IP), flags, admin duplicate signals, suspend switch
Marty, 2026-09-16, after @megamol created megamol2/megamol3 under his own link and bought $20 on each
to fake his two qualifying buyers. fraud.js records sign-up IP/UA/browser id (iap.dev cookie set with
the code request) and last-seen on sign-in. New accounts: dup-device (browser already has an account)
and sponsor-device are refused, ip-burst (> fraudMaxSignupsPerIpDay, default 2, per 24h) is refused;
sponsor-ip and shared-ip are flagged only. Flagged/suspended accounts never count on the leaderboard
and cannot adopt from the tank; suspended accounts are signed out everywhere (auth.fromRequest
wrapper) and refused at sign-in. Admin > Members: Duplicate signals card (shared browser / IP,
flagged, suspended), flags badge, Suspend/Unsuspend; GET /api/admin/fraud; PATCH members {suspend,
reason, flags}. Telegram admin alert on every block/flag. Privacy page + chatbot prompt updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 14:39:13 -05:00
martbost 125be174e3 Terms: one account per person (duplicate accounts and self-referral prohibited; Qualified Start linked wallets are the only sanctioned extra positions); chatbot canned + prompt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 14:29:54 -05:00
martbost 5641582709 Banner creatives must be images: server-side image check (extension or image/* HEAD), form hint, chatbot note
Campaign #134 (NAS ad 2806) had the member's join-page link in the image field and served a broken
banner 141 times on the network; #102 did the same with an imgbb page link. imageCheck() accepts
/uploads/ and instantadpay.com/banners files and image extensions outright, otherwise HEADs the URL
(one redirect) and requires image/*. Wired into member and house campaign creation for banners and
login-ad creatives. Form placeholder + hint point at Promo tools > Banners > Copy image URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 13:56:30 -05:00
martbost d6bc79c9d6 Home: 'Watch the 4-minute overview' CTA + inline overview video section (Spaces training/platform-overview.mp4)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 13:42:23 -05:00
martbost 230c250c64 Earnings pane: Trace a payment card sits at the top
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 12:30:46 -05:00
martbost a727ab1ce5 Payment trace: self-serve "who was paid, who was skipped and why" per purchase (Earnings tab + admin member card)
Marty, 2026-09-16, after the third "why didn't my sponsor get paid" thread of the day (livedreams / gracie25,
Morten / Terry's linked wallets). GET /api/my/trace?who=<#|username> (yourself, anyone up to 3 levels
below you, or your own 3 uplines) and GET /api/admin/trace?who= list every purchase the member was
part of, newest first: buyer, sponsor, package, then levels 1-3 with the recipient, the skipped
positions and the reason ("not qualified: had N of 2 qualifying buyers then"), the platform share, and
a verify link. Linked extra wallets are named after their owner. Earnings tab card "Trace a payment";
admin member card gets a "Payment trace" section; chatbot canned answer + AI prompt route the
question there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 12:24:53 -05:00
martbost dffcaa113b Never hold a sale for an unactivated sponsor: walk up to the first payable upline, referral follows the money
Marty, 2026-09-16: a purchase must never wait on a sponsor who has not linked a wallet / switched on
payouts. /api/sponsor and /me now walk the site's sponsor line upward to the first upline that is
activated with payouts on (not on the no-payout list) and the buy proceeds under them; nobody
activated -> the catch position (#1). The buyer sees a one-line notice and carries on; admin gets a
heads-up. When the position is created on-chain (MemberActivated), sponsorSyncOnEvent re-points the
buyer's stored sponsor to whoever was paid (#1 included), so the referral leaves the skipped
sponsor's line for good, then sends the pointed notices: the skipped sponsor(s) are told they lost
this referral permanently and how to switch on payouts; the sponsor who was paid is told the
referral is theirs for good and to coach the one who missed it (teach-forward). Admin lookup:
GET /api/admin/sponsor-resolve?ref=. Only a transient chain error still pauses a buy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:18:14 -05:00
martbost dda8c976b0 No native dialogs: Shorts report prompt -> inline reason buttons; admin drip confirms -> IAP.confirmBox
Same class of bug as the campaign #27 false report: the Shorts report was a prompt() pre-filled with
'inappropriate'. Members now tap a reason button on purpose or cancel. Script tags bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 10:23:00 -05:00
martbost 616f328db5 Report-an-ad dialog: site dialog with no default reason (was a native prompt pre-filled with "broken")
Campaign #27 (house AdRevSplit text ad) was reported "broken" with no note while the target framed
and loaded fine; the report dialog was prompt() with 'broken' as the default value, so one stray OK
filed it. Now IAP.ask with an explicit, validated reason and an optional note; common.js tag bumped
on all pages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 10:21:53 -05:00
martbost 69d672665c Credit activity ledger: every earn and spend logged with a reason, listed under Campaigns
Hugh earned a 10-credit sign-in bonus, then bought a 280-credit extension; earned credits spend
first, so Earned read 0 and the bonus looked lost. There was no record a member could check.
Now every real credit movement writes a credit_log row (JSON store in file mode): welcome, sign-in
bonus with streak, daily claim, inbox reads, video watches, verified visits, milestone/leaderboard
bonuses, partner codes, admin/team grants, AI Copy Engine charges + refunds, flat buys (featured
run, extension, visit pack, more visits), login-ad days, and metered delivery rolled up per
campaign per day; on-chain purchased-credit spends are marked. GET /api/my/credits/activity;
dashboard shows the last 40 under Campaigns (also when there are no campaigns) with a link from
Earn credits. Chatbot canned answer + AI prompt updated; my.js tag bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 08:41:43 -05:00
martbost ffacf741da Extend run / visits top-up: budget = charged + this buy (release parked credits); featured row shows days left
Hugh extended his featured link and his balance fell 1,133 -> 0: the row still carried the 1,400
parked by the old add-credits button, and reactivating it reserved that budget again. Both flat
paths now set budget to spent + accrued + cost. Featured Run column shows "N of M days left"
(Hugh: "says 14 but it's actually 7") and "M days done" after the run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 08:19:33 -05:00
martbost 28bce75913 Featured links: Extend run (book more days) replaces Buy more views; visit packs top up by visits; campaign rows say paid up front and runs through
Hugh and Michael both added credits to a featured run expecting more days. Featured is a flat
daily buy, so the credits sat unspent until the run ended and their balance read 0 meanwhile.
Now: /api/my/campaigns/:id/extend books extra days after the current run (or from today if it
ended) and charges them at once; top-up refuses featured; a visits top-up buys more visits at the
pack rate, charged now; rows show paid up front + day count + runs through <date> / run ended;
explainer under the campaign table; chatbot canned answer + AI prompt updated; my.js v tag bumped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 07:38:37 -05:00
martbost b7cc06b28c Overview: click a chip in Your line at a glance to open that member's Activity panel on My line
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 18:54:00 -05:00
martbost 0da7c796b8 Campaigns: visit packs show 'paid up front' and delivered-of-total instead of a spent bar (Michael's 3,000-credit pack looked used up)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 16:13:26 -05:00
martbost 4d578fadac Lead with the $20 activation language; free-member POL earning no longer volunteered (Marty); chatbot answers truthfully only when asked point-blank
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 15:00:01 -05:00
martbost 310384cdad Credits versus POL spelled out: level 1 needs payouts on (no purchase), free members earn credits; home page block, earning page, join step 3, chatbot (Clinton's question)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 14:29:39 -05:00
martbost 92e7304882 Daily growth snapshot to the Telegram payments feed and shared topic (sign-ups, purchases, POL paid, campaigns, viewing, lifetime totals); admin preview/send-now routes and settings
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:43:19 -05:00
martbost 560d758ed9 Pipeline: count linked positions' buyers on member cards, the way My line and badges do; show the main/positions split on the card
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:22:25 -05:00
martbost 02f742f90a Pipeline: sponsor follow-up board (stages from the ledger, notes/follow-ups/tags, stage messages into chat), gated by pipelineMode with a coming-soon card until launch; training cards can wait on the same switch
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 11:01:03 -05:00
martbost 87fa837165 Campaigns table: click sources stacked so the Clicks column stays narrow, card layout up to 900px; audit alert-day persisted, rounding-scale diffs ignored
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 07:31:16 -05:00
martbost 653c6d486e Launch checklist: launch graphics (wide + square), five day-by-day posts and a DM with link and FOUNDER code, copy buttons
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 07:12:23 -05:00