S3 (a1): affiliate + partner plumbing

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>
This commit is contained in:
Kev
2026-07-11 14:28:18 -04:00
parent e4d2e79f95
commit 0996320bd1
23 changed files with 1151 additions and 36 deletions
+11 -5
View File
@@ -1,20 +1,26 @@
// Session 52 — Push-to-Book "Coming Soon" teaser (source-asserted).
// Session 52 shipped BOOK IT as a "coming soon" teaser. A1 Session 3
// made it a REAL sportsbook deep link (organic until affiliateConfig
// flips a book on). GradeResultCard's PUSH-TO-BOOK (account linking /
// bet-slip push) remains a genuine teaser — that feature is unbuilt.
const fs = require('fs');
const path = require('path');
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
describe('BOOK IT teaser', () => {
it('StatStrip renders the "BOOK IT" teaser on graded props', () => {
describe('BOOK IT deep link (was the S52 teaser)', () => {
it('StatStrip renders BOOK IT as a real anchor built by bookLinks', () => {
const src = read('components/vyndr/StatStrip.tsx');
expect(src).toContain('BookItTeaser');
expect(src).toContain('BOOK IT ⟶');
expect(src).toContain('Push-to-Book coming soon');
expect(src).toContain('<BookItTeaser p={p} />');
expect(src).toContain('buildBookLink({ book: target, player, sport })');
expect(src).toContain('rel={BOOK_LINK_REL}');
// the dead-span teaser copy is gone
expect(src).not.toContain('Push-to-Book coming soon');
});
it('GradeResultCard renders "PUSH-TO-BOOK · COMING SOON"', () => {
it('GradeResultCard still renders the honest "PUSH-TO-BOOK · COMING SOON" teaser', () => {
const src = read('components/vyndr/GradeResultCard.tsx');
expect(src).toContain('PUSH-TO-BOOK · COMING SOON');
expect(src).toContain('Connect DraftKings, FanDuel, BetMGM');