Wave 1: wire /intelligence, /slip, /parlay + /marketplace honesty pass
Wiring + one copy pass. No grade, ledger, model or scoring change (diff empty
across intelligence/, ledgerService, outcomeService, gradeSlateService).
REVIEW ZERO — each surface proven with real data BEFORE wiring:
0.1 /intelligence vs /system are NOT duplicates. System.dc.html is a
multi-surface artboard (TERMINAL + INTELLIGENCE + WIRE sections), not the
design for a distinct /system route; its INTELLIGENCE section is already
realised as the live app/intelligence/page.tsx. No /system page exists and
none should be built as a second copy — the prod 404 is correct.
0.2 /intelligence renders live and gates SERVER-side, not by blur: the proxy
requires auth and limits by tier (desk 50 signals / non-desk 8), and
returns 401 to an anonymous caller (verified live). No leak.
0.3 /slip parses a real DraftKings slip end to end: 3/3 legs,
needs_review false, Aaron Judge total_bases over 1.5 @ -115. Honest limit
recorded: parsers are layout-rigid, an unsupported layout yields ZERO legs
rather than wrong ones (never-guess), so real-world OCR hit-rate across
layouts is CANNOT DETERMINE until user slips arrive.
0.4 /parlay direct route hits the real correlation builder on the same
ParlayContext the drawer uses.
0.5 /marketplace advertised four unbuilt things but made NO performance or
profit claim, and its capture was already real (/api/waitlist upserts to a
waitlist table). The gap was tense, not fabrication.
WIRED: Nav MORE gains Intelligence, Slip Reader and Marketplace; Parlay Lab
re-pointed from the drawer hash to /parlay (the drawer is unaffected —
ParlayPanel stays mounted with its floating badge).
GATING: /intelligence added to GATED_ROUTES because its feed 401s signed-out, so
an ungated link would land visitors on a permanently empty page. /parlay stays
OPEN deliberately — it is the free parlay funnel and gating it would be a
monetization regression.
/marketplace honesty pass: every item body now opens "Not built yet." /
"Not written yet." / "Not produced yet." with what is planned; the subhead states
it is not a purchase, not a pre-order and not a promise of a ship date; the
playbook item carries "No profit claim, no promised return". The capture stays
real — no fake button. Unit-locked.
Floor: 315 suites / 3920 tests green (12 new), web build exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# SPEC — WAVE 1: WIRE THE WORKING SURFACES + /marketplace HONESTY (shipped)
|
||||
2026-07-31. Wiring + one copy pass. No grade, ledger, model, or scoring change.
|
||||
|
||||
## REVIEW ZERO — each surface proven BEFORE wiring
|
||||
|
||||
- **0.1 `/intelligence` vs `/system` — NOT duplicates; nothing to resolve away.** `System.dc.html`
|
||||
is a MULTI-surface artboard (it contains TERMINAL, INTELLIGENCE and WIRE sections), not the design
|
||||
for a distinct `/system` route. Its INTELLIGENCE section is **already realised** as the live
|
||||
`app/intelligence/page.tsx`. **No `/system` page exists and none should be built as a second copy** —
|
||||
the prod 404 is correct. Only `/intelligence` was wired. ✅
|
||||
- **0.2 `/intelligence` renders live + gates correctly.** Real feed (`/api/intelligence/feed`), 160
|
||||
lines, zero placeholders. **Entitlement is enforced SERVER-side, not by blur**: the proxy requires
|
||||
auth and limits by tier — **desk 50 signals / non-desk 8** — and returns `401 Not signed in` to an
|
||||
anonymous caller (**verified live in prod**). So making it nav-reachable leaks nothing. ✅
|
||||
- **0.3 `/slip` functions END TO END on a real slip.** Parsed the real DraftKings fixture:
|
||||
**3/3 legs, `needs_review: false`**, leg 1 = `Aaron Judge · total_bases · over 1.5 @ -115`. ✅
|
||||
**Honest limit recorded:** the parsers are layout-rigid. A slip in an unsupported layout yields
|
||||
**zero legs rather than wrong ones** — correct per the NEVER-GUESS contract, but it means real-world
|
||||
OCR hit-rate across layouts is **CANNOT DETERMINE** until real user slips arrive.
|
||||
- **0.4 `/parlay` direct route hits the working surface.** `app/parlay/page.tsx` is the real
|
||||
correlation builder on the **same `ParlayContext`** the drawer uses — not a separate unbuilt page. ✅
|
||||
- **0.5 `/marketplace`** advertises four unbuilt things (API access · custom alerts · "The Edge"
|
||||
playbook · merch capsule). **No performance/edge/profit claim was present** (the worse category —
|
||||
clean). The capture was already REAL: `fetch('/api/waitlist')` → `src/routes/waitlist.js` upserts to
|
||||
a `waitlist` table. The gap was tense: bodies read as present-tense descriptions.
|
||||
|
||||
## PHASE 1 — WIRED (all three passed)
|
||||
`Nav.tsx MORE`: **Intelligence**, **Slip Reader**, **Marketplace** added; **Parlay Lab re-pointed from
|
||||
`#parlay` (drawer-only) to `/parlay`** (the real page). The drawer is unaffected — `ParlayPanel` stays
|
||||
mounted in the layout with its floating badge, its natural in-context use from a prop row.
|
||||
|
||||
**Gating change (one line, principled):** `/intelligence` added to `GATED_ROUTES`. Its feed 401s
|
||||
signed-out, so an ungated nav link would land visitors on a permanently empty page. `/slip` was already
|
||||
gated (personal surface). **`/parlay` deliberately stays OPEN** — it is the free parlay funnel and
|
||||
gating it would be a monetization regression (same rule as dashboard/scan).
|
||||
|
||||
## PHASE 2 — /marketplace HONESTY PASS
|
||||
Every item body now opens **"Not built yet."** / "Not written yet." / "Not produced yet." followed by
|
||||
what is *planned*. The subhead is explicit: *"None of the below is built yet… joining a list records
|
||||
your interest, nothing more. It is not a purchase, not a pre-order, and not a promise of a ship date."*
|
||||
The playbook item carries **"No profit claim, no promised return."** The capture stays REAL (no fake
|
||||
button). Unit-locked: every body must contain a not-yet marker, and the page must carry no
|
||||
guaranteed/win-rate/ROI/beat-the-book language.
|
||||
|
||||
## PHASE 3 — VERIFY
|
||||
Grade, ledger, model, scoring **untouched** (diff empty across `intelligence/`, `ledgerService`,
|
||||
`outcomeService`, `gradeSlateService`). **315 suites / 3,920 tests green** (12 new), **web build exit 0**.
|
||||
|
||||
**Chrome audit manifest — Wave 1 additions:**
|
||||
| surface | state | check |
|
||||
|---|---|---|
|
||||
| `/intelligence` | **signed-out** → should redirect to `/login` (now gated) | no empty-feed page |
|
||||
| `/intelligence` | **free** vs **ENTITLED (Desk)** | free sees 8 signals + lock overlay; **Desk sees 50** — *entitled path needs Kev* |
|
||||
| `/slip` | **free (signed-in)** | upload/paste → editable legs; unsupported layout → empty, not wrong |
|
||||
| `/parlay` | **logged-out** | direct nav entry loads the full lab; free 2-leg cap + payout blur |
|
||||
| `/marketplace` | logged-out | "Not built yet" on every item; Join waitlist records and confirms |
|
||||
|
||||
## TAGS
|
||||
VERIFIED: 0.1–0.5, all wiring, gating, copy. **CANNOT DETERMINE: real-world slip OCR hit-rate across
|
||||
book layouts** (no user slips yet). BLOCKED: none.
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* WAVE 1 — surfaces wired only after being proven to work with real data
|
||||
* (specs/wave1-wiring.md). Existence != works: the populated-path crash and the
|
||||
* single-book default both came from surfaces that "existed" but had never run.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const read = (p) => fs.readFileSync(path.join(__dirname, '../../', p), 'utf8');
|
||||
const routes = require('../../web/src/lib/routes');
|
||||
|
||||
describe('the three proven surfaces are reachable from nav', () => {
|
||||
const nav = read('web/src/components/Nav.tsx');
|
||||
|
||||
test('/intelligence, /slip and /marketplace have nav entries', () => {
|
||||
expect(nav).toMatch(/href: '\/intelligence'/);
|
||||
expect(nav).toMatch(/href: '\/slip'/);
|
||||
expect(nav).toMatch(/href: '\/marketplace'/);
|
||||
});
|
||||
|
||||
test('Parlay Lab points at the REAL page, not only the drawer hash', () => {
|
||||
expect(nav).toMatch(/\{ label: 'Parlay Lab', href: '\/parlay' \}/);
|
||||
expect(nav).not.toMatch(/href: '#parlay', action: 'parlay'/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gating is correct for the newly-public surfaces', () => {
|
||||
test('/intelligence is GATED — its feed 401s signed-out, so an ungated link would land on an empty page', () => {
|
||||
expect(routes.GATED_ROUTES).toContain('/intelligence');
|
||||
expect(routes.isGatedRoute('/intelligence')).toBe(true);
|
||||
});
|
||||
|
||||
test('/slip stays gated (personal surface)', () => {
|
||||
expect(routes.GATED_ROUTES).toContain('/slip');
|
||||
});
|
||||
|
||||
test('/parlay stays OPEN — it is the free parlay funnel, gating it is a monetization regression', () => {
|
||||
expect(routes.OPEN_ROUTES).toContain('/parlay');
|
||||
expect(routes.isGatedRoute('/parlay')).toBe(false);
|
||||
});
|
||||
|
||||
test('no route is both gated and open', () => {
|
||||
const overlap = routes.GATED_ROUTES.filter((r) => routes.OPEN_ROUTES.includes(r));
|
||||
expect(overlap).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/slip actually parses a real slip end to end (the works-with-real-data proof)', () => {
|
||||
const { parseSlipText } = require('../../src/services/slipReader');
|
||||
const fixture = fs.readFileSync(
|
||||
path.join(__dirname, '..', 'fixtures', 'slips', 'draftkings.txt'), 'utf8',
|
||||
);
|
||||
|
||||
test('extracts every leg from the real DraftKings grammar', () => {
|
||||
const out = parseSlipText(fixture);
|
||||
expect(out.book).toBe('draftkings');
|
||||
expect(out.legs).toHaveLength(3);
|
||||
expect(out.needs_review).toBe(false);
|
||||
expect(out.legs[0]).toMatchObject({ player: 'Aaron Judge', stat: 'total_bases', line: 1.5, side: 'over', odds: -115 });
|
||||
});
|
||||
|
||||
test('an UNRECOGNISED layout yields NO legs rather than wrong ones (never-guess)', () => {
|
||||
// A layout the parser does not support must degrade to empty, not fabricate.
|
||||
const out = parseSlipText('DraftKings\nAaron Judge\nHome Runs\nOver 0.5 +285');
|
||||
expect(out.legs).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/marketplace is honest interest-capture', () => {
|
||||
const src = read('web/src/app/marketplace/page.tsx');
|
||||
|
||||
test('every item states it is NOT built yet', () => {
|
||||
const bodies = src.match(/body: '[^']+'/g) || [];
|
||||
expect(bodies.length).toBeGreaterThanOrEqual(4);
|
||||
for (const b of bodies) expect(b).toMatch(/Not built yet|Not written yet|Not produced yet/);
|
||||
});
|
||||
|
||||
test('the page says plainly it is not a purchase, pre-order, or ship-date promise', () => {
|
||||
expect(src).toMatch(/None of the below is built yet/);
|
||||
expect(src).toMatch(/not a pre-order/);
|
||||
});
|
||||
|
||||
test('makes NO performance / edge / profit claim', () => {
|
||||
expect(src).toMatch(/No profit claim, no promised return/);
|
||||
expect(src).not.toMatch(/guaranteed|win rate|ROI of|beat the book/i);
|
||||
});
|
||||
|
||||
test('the capture is REAL — it posts to the live waitlist endpoint', () => {
|
||||
expect(src).toMatch(/fetch\('\/api\/waitlist'/);
|
||||
expect(fs.existsSync(path.join(__dirname, '../../web/src/app/api/waitlist/route.ts'))).toBe(true);
|
||||
});
|
||||
});
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -6,22 +6,22 @@ const ITEMS = [
|
||||
{
|
||||
list: 'api',
|
||||
title: 'API access',
|
||||
body: 'Programmatic access to the grading engine. Build your own dashboards, alerts, automations.',
|
||||
body: 'Not built yet. Planned: programmatic access to the grading engine so you can build your own dashboards, alerts and automations.',
|
||||
},
|
||||
{
|
||||
list: 'alerts',
|
||||
title: 'Custom alerts',
|
||||
body: 'Get pinged when specific players, stats, or matchups hit your threshold — by SMS, email, or webhook.',
|
||||
body: 'Not built yet. Planned: get pinged when specific players, stats or matchups hit a threshold you set — by SMS, email or webhook.',
|
||||
},
|
||||
{
|
||||
list: 'The Edge',
|
||||
title: 'The Edge — playbook',
|
||||
body: 'Long-form analysis of the model and how to deploy it in real bankroll management.',
|
||||
body: 'Not written yet. Planned: long-form analysis of how the model works and how to think about bankroll. No profit claim, no promised return.',
|
||||
},
|
||||
{
|
||||
list: 'merch',
|
||||
title: 'Capsule drop',
|
||||
body: 'First merch run. 200 units. Founder access gets first pull.',
|
||||
body: 'Not produced yet. Planned: a first merch run, founder access gets first pull. Unit count not fixed.',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -60,7 +60,9 @@ export default function MarketplacePage() {
|
||||
The VYNDR Marketplace.
|
||||
</h1>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: 16, maxWidth: 560, margin: '0 auto' }}>
|
||||
API access, custom alerts, deep analysis, and limited drops. Reserve your spot — these go to founder users first.
|
||||
None of the below is built yet. This page exists so you can tell us which ones you'd
|
||||
actually use — joining a list records your interest, nothing more. It is not a purchase,
|
||||
not a pre-order, and not a promise of a ship date.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -24,8 +24,16 @@ const PRIMARY = [
|
||||
];
|
||||
const MORE = [
|
||||
{ label: 'Explore', href: '/explore' },
|
||||
// Session 60 (4.4) — discoverable Parlay Lab entry (opens the drawer).
|
||||
{ label: 'Parlay Lab', href: '#parlay', action: 'parlay' as const },
|
||||
// Wave 1 (2026-07-31) — the Parlay Lab entry now points at the REAL page
|
||||
// (app/parlay/page.tsx, the full correlation builder) rather than only opening
|
||||
// the drawer. The drawer is unaffected: ParlayPanel stays mounted in the layout
|
||||
// with its floating badge, which is its natural in-context use from a prop row.
|
||||
{ label: 'Parlay Lab', href: '/parlay' },
|
||||
// Wave 1 — proven-before-wired. /intelligence renders a real, server-tier-limited
|
||||
// feed; /slip parses a real slip end to end (3/3 legs on the DraftKings grammar).
|
||||
{ label: 'Intelligence', href: '/intelligence' },
|
||||
{ label: 'Slip Reader', href: '/slip' },
|
||||
{ label: 'Marketplace', href: '/marketplace' },
|
||||
// Compare is in development (no real two-player feed yet) — no nav entry until
|
||||
// it renders real data. The route still resolves to an honest in-dev state.
|
||||
{ label: 'Tracker', href: '/tracker' },
|
||||
|
||||
@@ -25,6 +25,11 @@ const GATED_ROUTES = [
|
||||
'/notifications',
|
||||
'/invite',
|
||||
'/slip', // A1 S9 — slip reader (personal surface: reads the user's own bet slip)
|
||||
// Wave 1 (2026-07-31) — /intelligence became nav-reachable. Its feed REQUIRES auth
|
||||
// (`/api/intelligence/feed` returns 401 signed-out, verified live) and limits by tier
|
||||
// SERVER-side (desk 50 signals / non-desk 8) — so it never blurred-over full data.
|
||||
// Gated so a signed-out visitor lands on /login instead of a permanently empty feed.
|
||||
'/intelligence',
|
||||
];
|
||||
|
||||
/* Open — reachable without auth (marketing + the free funnel + auth itself). */
|
||||
|
||||
Reference in New Issue
Block a user