0996320bd1
Zero out-of-pocket; everything config-flip-ready but DISABLED/organic. - BOOK IT deep links: web/src/lib/bookLinks.js + affiliateConfig.js (all books enabled:false, Impact/Partnerize param shapes documented, empty params skipped). Wired into StatStrip BookItTeaser (real anchor now) + scan hand-off links. Every book anchor renders rel="sponsored noopener noreferrer" (BOOK_LINK_REL). - Best-price marker: slateAdapter.detectBestBook (only when >=2 books post the SAME line and prices differ — absent beats wrong) + subtle signal-green dot in StatStrip. Slate.groupByGame threads the grouped per-book rows (books[]) onto PropRowProp instead of discarding them. - Partner refs: ?ref=CODE -> vyndr_ref cookie (90d, first-touch, PartnerRefCapture in layout) -> signup metadata partner_ref -> internal GET /api/partners/report/:code (requireInternalAuth; honest zeros + note until the TODO migration in docs/PARTNERS.md adds user_profiles.partner_ref — NOT run). Stripe promo-code convention: partner code == promotion code, verbatim. - Tests: +41 (2398 -> 2439, 209 suites); bookItTeaser + vyndrCoreScreens invariants updated to the new (stronger) rel contract. Web build exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.0 KiB
4.0 KiB
A1 Session 3 — Affiliate + Partnership Plumbing
Operating constraints
- ZERO out-of-pocket. No affiliate program is approved yet — everything ships config-flip-ready but DISABLED / organic by default.
- Data semantics: never fabricate market values. A "best price" claim renders only when genuinely multiple books post the same line — absent beats wrong.
- No exclamation points in any copy (VOICE v1.1).
1. BOOK IT deep-link builder
web/src/lib/bookLinks.js(CommonJS, unit-testable)SUPPORTED_BOOKS: draftkings, fanduel, betmgm, caesars, betrivers (hosts sourced from the existing SPORTSBOOKS list in scan/page.tsx + lib/books.js brand map).buildBookLink({ book, player, sport, state? }, config?) → { url, tracking } | null- Unknown book → null (absent beats wrong).
- Organic base:
https://{host}/?search={player}(existing deep-link shape). - Affiliate layer: reads
web/src/lib/affiliateConfig.js— per book{ enabled: false, params: {} }with Impact/Partnerize-style param shapes documented in comments (irclickid / sharedid / afid / btag). tracking: trueonly when the book is enabled AND at least one non-empty param was appended. Disabled → clean organic link.
BOOK_LINK_REL = 'sponsored noopener noreferrer'— every book anchor renders this rel.
- Wiring:
StatStrip.tsxBookItTeaser → real<a>deep link (best-price book when known, else the prop's book, else DraftKings). Organic until config flips.scan/page.tsxsportsbook hand-off anchors →buildBookLink+BOOK_LINK_REL(replaces the local SPORTSBOOKS/deepLink pair).
2. Best-line highlight (cheap version)
- Finding (documented in the session report): the grouped odds proxy already
ships per-book rows to the browser as
lines[]per player+stat;Slate.groupByGame/pickLinecollapse to ONE line and discard the rest. - Ship: thread
books(the rawlines[]) onto each PropRowProp;slateAdapter.detectBestBook(rows, side, refLine)returns{ book, odds }ONLY when ≥2 books post the SAME line for the side and prices differ;buildPlayerStripsFromPropsattachesbestBook+bookto each strip prop; StatStrip renders a subtle signal-green dot (one meaning: best available price).
3. Partner ref system
- (a)
web/src/lib/partnerRef.js(CommonJS) — parse?ref=CODE, sanitize (A–Z 0–9 - _, ≤32, uppercased), first-party cookievyndr_ref(90d, SameSite=Lax, Path=/), first-touch (never overwrites an existing cookie).components/vyndr/PartnerRefCapture.tsxmounted in the layout (GlobalHosts pattern). - (b)
src/routes/partners.js—GET /api/partners/report/:codebehindrequireInternalAuth→{ signups, conversions, mrr_attributed }read fromuser_profilesfiltered bypartner_ref. The column does NOT exist yet: the endpoint degrades to zeros + a note, and the TODO migration is documented in docs/PARTNERS.md (NOT run). - (c) Signup:
AuthContext.signUpforwards thevyndr_refcookie into Supabase signup metadata aspartner_ref. - (d)
docs/PARTNERS.md— Stripe promo-code mapping convention + the TODO migration.
Acceptance criteria
- All book links organic by default; flipping
enabled+ params in affiliateConfig produces a tracked URL with the params appended. - Best-price dot never renders on single-book or unequal-line data.
?ref=CODEfirst visit setsvyndr_reffor 90 days; signup metadata carriespartner_ref; report endpoint 401s without the internal key.- Full
npx jestgreen;cd web && npx next buildexit 0.
Test plan
- tests/unit/bookLinks.test.js — organic default, enabled param shapes, aliases, betrivers state, unknown book, rel constant + source assertions.
- tests/unit/slateAdapterBestBook.test.js — detectBestBook honesty rules + strip attachment.
- tests/unit/partnerRef.test.js — sanitize/parse/cookie/first-touch.
- tests/unit/partnersRoute.test.js — report math + internal-auth mount.