Honesty pass: remove every live fabrication (REMOVE/HIDE only, no feature cut)
Six live untruths corrected — no grade/snapshot/scorer/pipeline touched: 1. /compare — hardcoded Jokic A+/Wembanyama A + fake VERDICT replaced with an honest in-development state; removed from Nav + BottomTabBar (route still resolves, never the sample). Real two-player build is later. 2. Pricing — founder Desk $34.99→$44.99 (matches lib/checkout.js), Analyst $14.99; removed the struck $19.99/$44.99 "regular" numbers and DeskShowcase's stale $34.99. First-100 counter is real (ClaimMeter → Stripe countFounderSeats); no fake "first 50" desk claim added (no such counter exists). 3. FAQ "NexaPay" → Stripe (verified: live checkout is Next→Express→checkout.stripe.com). 4. FAQ + Features "Brier/CLV published from day one" removed (not surfaced yet) — returns when real. Backend Brier compute untouched. 5. MobileEdgeBoard removed from the Slate — its edge% feed was a miscalibrated placeholder (masked >40% as "—"); phones now show the real game cards. 6. Price triplet — never-computed model/EV now derives NO_MODEL (honest absent, MODEL "—" / "NOT PRICED", no verdict) instead of QUARANTINE's false "we suppressed our price / a leg is poisoned" copy. Fixes grade card + LiveHeroProp. Full suite 3833 green, web build exit 0. Tests updated to the new honest contracts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VsztNChZ7vEvSR61AuMhD1
This commit is contained in:
@@ -35,7 +35,8 @@ const TABS: TabDef[] = [
|
||||
];
|
||||
|
||||
const MORE_ITEMS = [
|
||||
{ label: 'Compare', href: '/compare' },
|
||||
// Compare removed from nav — in development, no real feed yet (route still
|
||||
// resolves to an honest in-dev state, never the old sample matchup).
|
||||
{ label: 'Tracker', href: '/tracker' },
|
||||
{ label: 'The Report', href: '/blog' },
|
||||
{ label: 'Invite Friends', href: '/invite' },
|
||||
|
||||
@@ -9,7 +9,7 @@ const FAQS = [
|
||||
},
|
||||
{
|
||||
q: 'How accurate is the model?',
|
||||
a: 'Check the ledger. Every grade, every result, updated nightly. We don\'t hide misses. Brier score and CLV are tracked from day one and published.',
|
||||
a: 'Check the ledger. Every grade, every result, updated nightly. We don\'t hide misses. Hit rate is published by grade tier once the sample is large enough to mean something.',
|
||||
},
|
||||
{
|
||||
q: 'What sports do you cover?',
|
||||
@@ -21,11 +21,11 @@ const FAQS = [
|
||||
},
|
||||
{
|
||||
q: 'What is the Founder Access price?',
|
||||
a: 'First 100 users lock $14.99/mo for life. After that the price moves to $24.99/mo and never comes back to $14.99. Locked-in pricing carries if you maintain continuous subscription.',
|
||||
a: 'First 100 users lock $14.99/mo for life. When the 100 founder seats are gone, standard pricing applies. Locked-in pricing carries if you maintain continuous subscription.',
|
||||
},
|
||||
{
|
||||
q: 'How is payment processed?',
|
||||
a: 'We use NexaPay. You pay with Visa, Mastercard, Apple Pay, or Google Pay. We never see your card data. We are PCI-out-of-scope.',
|
||||
a: 'We use Stripe. You pay with Visa, Mastercard, Apple Pay, or Google Pay. We never see your card data. We are PCI-out-of-scope.',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const FEATURES = [
|
||||
{
|
||||
icon: '⌦',
|
||||
title: 'The honest ledger',
|
||||
body: 'Every grade. Every result. No hiding. No deletion. Brier score and CLV from day one. Public accuracy by tier.',
|
||||
body: 'Every grade. Every result. No hiding. No deletion. Public accuracy by grade tier, once the sample is large enough to mean something.',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ 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 },
|
||||
{ label: 'Compare', href: '/compare' },
|
||||
// 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' },
|
||||
{ label: 'The Report', href: '/blog' },
|
||||
{ label: 'Invite', href: '/invite' },
|
||||
|
||||
@@ -68,8 +68,10 @@ const TIERS: TierConfig[] = [
|
||||
{
|
||||
id: 'analyst',
|
||||
name: 'Analyst',
|
||||
// Founder price. First 100 seats lock $14.99 for life (real Stripe-synced
|
||||
// counter via ClaimMeter). No struck "regular" number: the post-founder
|
||||
// price is not wired, and we do not promise a figure that isn't.
|
||||
price: '$14.99',
|
||||
originalPrice: '$19.99',
|
||||
cadence: '/mo',
|
||||
badge: 'Founder Access',
|
||||
headline: 'The full intelligence layer.',
|
||||
@@ -91,11 +93,11 @@ const TIERS: TierConfig[] = [
|
||||
{
|
||||
// DS5 (Part 6, #8) — Desk is THE hero tier. It carries the value-showing
|
||||
// story and the single primary CTA on the grid (color contract #9: never
|
||||
// two competing green CTAs). $44.99 regular, $34.99 for founders.
|
||||
// two competing green CTAs). Founder Desk = $44.99 (matches lib/checkout.js).
|
||||
// No struck "regular" number: the post-founder desk price is not wired.
|
||||
id: 'desk',
|
||||
name: 'Desk',
|
||||
price: '$34.99',
|
||||
originalPrice: '$44.99',
|
||||
price: '$44.99',
|
||||
cadence: '/mo',
|
||||
badge: 'Founder Desk',
|
||||
headline: 'The professional terminal. Everything the model knows.',
|
||||
|
||||
@@ -7,8 +7,7 @@ import { useRouter } from 'next/navigation';
|
||||
import VyndrGameCard, { type GameCardData } from '@/components/vyndr/GameCard';
|
||||
import type { SlateSport, GameLines, GameStreak } from '@/components/GameCard';
|
||||
import { PropRowProp, Tier } from '@/components/PropRow';
|
||||
import { isRelevantGame, detectBestLines, indexGrades, indexDeltas, buildPlayerStripsFromProps, formatGameTime, buildPitcherMap, pitchersForGameTeams, gradeKey, flattenToEdgeBoard, edgeBoardBreadth } from '@/lib/slateAdapter';
|
||||
import MobileEdgeBoard from '@/components/vyndr/MobileEdgeBoard';
|
||||
import { isRelevantGame, detectBestLines, indexGrades, indexDeltas, buildPlayerStripsFromProps, formatGameTime, buildPitcherMap, pitchersForGameTeams, gradeKey } from '@/lib/slateAdapter';
|
||||
// Wave 4A (Step 3) — OUTLOOK MODE: the never-empty grid. When there are no
|
||||
// live games (and it's not a fetch failure) the grid shows REAL data —
|
||||
// yesterday's proven receipts + tomorrow's date-pinned schedule.
|
||||
@@ -1205,34 +1204,22 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook
|
||||
{dateOffset === 0 && <MarketBreadth items={breadthItems} />}
|
||||
|
||||
{(() => {
|
||||
// Build each game's card once, then present two ways (Rev 3, screen 01):
|
||||
// MOBILE = Design's FLAT edge-ranked board (all props, one list, sorted
|
||||
// by edge); DESKTOP = the game-grouped cards. Same data, different IA —
|
||||
// toggled by width in globals.css (.edge-board-mobile / -desktop).
|
||||
// The game-grouped cards, shown identically on mobile and desktop.
|
||||
// The FLAT mobile "edge board" was removed (honesty pass): its edge%
|
||||
// feed was a miscalibrated placeholder, so it renders nowhere until a
|
||||
// real edge feed exists. Phones now show the same real game cards.
|
||||
const cards = orderedGames.map((g) => slateGameToCardData(g, gradeIndex, deltaIndex, pitcherMap, activeStat, viability, liveIndex));
|
||||
const edgeRows = flattenToEdgeBoard(cards);
|
||||
const breadth = edgeBoardBreadth(edgeRows, cards);
|
||||
const hasBoard = edgeRows.length > 0;
|
||||
return (
|
||||
<>
|
||||
{hasBoard && (
|
||||
<div className="edge-board-mobile" style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 12, overflow: 'hidden' }}>
|
||||
<MobileEdgeBoard rows={edgeRows} breadth={breadth} />
|
||||
</div>
|
||||
)}
|
||||
{/* Cards: on mobile they hide ONLY when the flat board is present
|
||||
(an ungraded slate still shows the game cards on phones). */}
|
||||
<div className={hasBoard ? 'edge-board-desktop' : ''} style={{ display: 'grid', gap: 16 }}>
|
||||
{cards.map((card, i) => (
|
||||
<VyndrGameCard
|
||||
key={`${card.sport}-${card.away.abbr}-${card.home.abbr}-${i}`}
|
||||
game={card}
|
||||
preferredBooks={preferredBooks}
|
||||
onOpen={() => router.push('/scan')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<div style={{ display: 'grid', gap: 16 }}>
|
||||
{cards.map((card, i) => (
|
||||
<VyndrGameCard
|
||||
key={`${card.sport}-${card.away.abbr}-${card.home.abbr}-${i}`}
|
||||
game={card}
|
||||
preferredBooks={preferredBooks}
|
||||
onOpen={() => router.push('/scan')}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
|
||||
@@ -229,6 +229,9 @@ export default function PriceTriplet({
|
||||
|
||||
const modelWithheld = state === STATES.QUARANTINE;
|
||||
const modelLocked = state === STATES.NO_VERDICT_LOCKED;
|
||||
// Honesty pass: model never computed → absent leg, honest note, NO verdict
|
||||
// (verdictFor returns null for this state), never QUARANTINE's "suppressed" copy.
|
||||
const modelAbsent = state === STATES.NO_MODEL;
|
||||
// GREEN = TAKEABLE EDGE ONLY. The model leg is green on exactly one state.
|
||||
const modelColor = state === STATES.VALUE ? 'var(--g-a)' : 'var(--text-0)';
|
||||
const verdict = verdictFor(state, vsFair, Number.isFinite(ev as number) ? (ev as number) : null);
|
||||
@@ -251,7 +254,7 @@ export default function PriceTriplet({
|
||||
<Leg
|
||||
label="MODEL"
|
||||
value={modelWithheld ? null : model}
|
||||
note={modelWithheld ? 'WITHHELD' : modelLocked ? 'ANALYST' : 'OUR PRICE'}
|
||||
note={modelWithheld ? 'WITHHELD' : modelLocked ? 'ANALYST' : modelAbsent ? 'NOT PRICED' : 'OUR PRICE'}
|
||||
color={modelColor}
|
||||
locked={modelLocked}
|
||||
compact={compact}
|
||||
|
||||
Reference in New Issue
Block a user