Session H (night2): design pass — §2 locks + honest waiting sweep
- QA.20: one meaning per color, locked — STEAM amber (caution), VALUE green (edge), red only at STALE/miss; revisions render the original grade struck through on slate AND ledger. - QA.21: every new data surface (PriorReads, PlayerStreaks, ModelRecord) renders data in mono (tabular-nums via the .mono class). - QA.22: waiting copy derives from the real cron schedule on every surface (StatStrip, dashboard, Explore) — no passive mystery anywhere. - Explore empty state joined the honest-waiting lib. - Sweep result: no raw brand hexes and no sans-serif data in tonight's components (sans only on prose/captions/hero letter — sanctioned); glow confined to A-tier badges + reveal chrome; 390px containment holds (wrapping chips, scroll-contained tables from Phase 3 rules). Backend 2352 -> 2396 tests (205 suites), web build exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,54 @@ work-order 1.6 closed (canonical player keys + slate join invariant),
|
||||
Phase 2 slate UX, Phase 3 mobile P0. DEPLOY GATE: Coolify by Sat 10 AM ET;
|
||||
first snapshot locks against freshly posted lines (manual internal trigger).
|
||||
|
||||
## Session 60 (night2/full-board, 2026-07-11) — BUILT ✅ THE WHOLE BOARD (awaiting "merge the train")
|
||||
|
||||
Branch `night2/full-board` off d10bb4c — ZERO pushes to main. 2352 →
|
||||
**2396 tests** (205 suites), web build exit 0. Sessions A–H all shipped:
|
||||
- **A** AUTONOMY.md + P0: SNAP_TTL 6h→24h (the 11h overnight cron gap
|
||||
expired the snapshot before the morning settle — accuracy loop silently
|
||||
settled nothing; the Postgres ledger settle was immune).
|
||||
- **B** Streaks engine RESURRECTED + fed: the snapshot pipeline now writes
|
||||
rosterlogs:{sport} from the stats it already fetches (producers were all
|
||||
external/unarmed — that's why panels never rendered). Form heat (hot
|
||||
hitters/sluggers/shooters, Σ/Σ rate math) + THE LENS (built-vs, tonight's
|
||||
matchup + opposing SP ERA, step-up/down, one-line read). LIVE acceptance:
|
||||
32 real rows (Turang 12-gm on-base, Reynolds 8-gm, Pratt 7-gm, Meidroth
|
||||
5-gm, Cortes 5-gm).
|
||||
- **C** Aggregator mounted: Explore = SEO server shell + hub (leaders +
|
||||
full streaks + hot lists); streaks FREE for all tiers (the picture is
|
||||
free); landing teaser fixed (pointed at off-season NBA → never showed);
|
||||
one stat selection filters props+streaks+hotlists; player-dossier
|
||||
ACTIVE STREAKS block (+?player= on /api/streaks).
|
||||
- **D** Phase 2.5: 20-min odds-only refresh in slate hours (skips snapshot
|
||||
slots, INTRADAY_REFRESH=0 kill switch, POST /api/internal/refresh/all).
|
||||
STEAM ▲ (with, no re-grade) / VALUE ▲ (against ≥1.0, re-grade holds) /
|
||||
PUBLIC revision (grade drops → revised_from_grade, strikethrough).
|
||||
Refresh-fidelity closing capture; ticker MOVEs at refresh frequency.
|
||||
Quota: ≤144 PropLine calls/day vs 9,000 free capacity. Post-deploy: set
|
||||
SNAPSHOT_EXPECTED_INTERVAL=1200 and SYNC goes live.
|
||||
- **E** Phase 4: /api/players/search built (didn't exist — the Ohtani bug);
|
||||
nameKey fuzzy match live-verified vs the real 1,299-player list.
|
||||
§7 reveal (steps → DECLASSIFIED stamp → 90ms staggered panels,
|
||||
reduced-motion safe). PRIOR READS chips. Parlay: shared statLabel,
|
||||
1-leg provisional grade, Nav entry via window.__openParlay.
|
||||
- **F** Phase 5: archetype definition line on-page; VYNDR-on-team
|
||||
(ModelRecord team scope); WNBA minutes-usage; Settings meter from the
|
||||
LIVE limiter (3/day free, 15/day analyst, ∞ desk); per-tier calibration
|
||||
chips (n≥20 PER TIER).
|
||||
- **G** Phase 6: landing first-paint root cause KILLED (page blocked its
|
||||
render on Supabase auth init for anonymous visitors; now a sync
|
||||
localStorage gate — anon paints instantly). Proof Strip: A-tier-gated
|
||||
header, "TONIGHT'S BOARD" fallback, yesterday's settled reads with
|
||||
outcome chips when ungraded. /api/content/top-signals + /streak-watch
|
||||
(zero-grade format) + /daily-report (self-flags do_not_post until n≥20).
|
||||
Per-player OG images (Node runtime) + player metadata.
|
||||
- **H** Design pass: QA.20–22 locks (STEAM amber/VALUE green/STALE-only
|
||||
red, revisions struck through, new data surfaces mono, honest waiting
|
||||
copy everywhere).
|
||||
|
||||
### The word: "merge the train" → main fast-forwards → Coolify ships board #2.
|
||||
|
||||
## Session 59 (2026-07-10 overnight) — SHIPPED ✅ ADDENDUM + 1.6 + PHASE 2 + PHASE 3
|
||||
|
||||
Backend 2327 → **2352 tests** (+25), 202 suites. Web build clean (exit 0).
|
||||
|
||||
@@ -773,6 +773,34 @@ phased plan in the Session-57 conversation / BUILD-STATE Next section).
|
||||
expands in place; GAME LINES <640px = `.gl-summary` best-line row +
|
||||
expander revealing `.gl-full.gl-expanded`.
|
||||
|
||||
## Night-2 Board (Session 60 — non-obvious)
|
||||
- **SNAP_TTL is 24h ON PURPOSE** (was 6h): the overnight cron gap is 11h;
|
||||
a shorter TTL silently kills the morning accuracy settle. AUTONOMY.md is
|
||||
the zero-touch trace — update it when the loop's shape changes.
|
||||
- **The snapshot pipeline FEEDS the aggregator**: `mergeRosterLogs` writes
|
||||
`rosterlogs:{sport}` (72h TTL, cap 300) from the same stats resolve that
|
||||
classifies archetypes. Streaks/hot-lists starve if you remove it — the
|
||||
external prefetch (n8n/tank01) was never armed.
|
||||
- **THE LENS RULE**: no raw streak renders alone — `streakLens.applyLens`
|
||||
attaches built-vs / tonight's matchup (+opposing SP ERA for MLB batters)
|
||||
/ difficulty / one-line read. Absent context = say less, never invent.
|
||||
Streaks are FREE for every tier; hot lists keep the free top-3 gate.
|
||||
- **Phase 2.5 lives in `intradayRefreshService`**: signed delta vs the
|
||||
GRADED side; STEAM never re-grades; ≥1.0 against re-grades THAT prop at
|
||||
the real current line; a dropped grade sets `revised_from_grade` ONCE
|
||||
(the original letter is forever). `applyRevision` only touches unsettled
|
||||
public rows. Scheduler skips full-snapshot slots; INTRADAY_REFRESH=0
|
||||
kills it. After deploy set SNAPSHOT_EXPECTED_INTERVAL=1200.
|
||||
- **/api/players/search is the canonical resolver** (nameKey fuzzy; MLB =
|
||||
statsapi list, others = cached names). The Python service is a fallback
|
||||
only. Never reintroduce raw-substring matching.
|
||||
- **Landing renders instantly for anon**: `hasStoredSession()` sync
|
||||
localStorage gate — do NOT re-block the marketing render on
|
||||
`useAuth().loading`; that was the 3–4s LOADING THE SLATE bug.
|
||||
- **QA.20–22 in vyndrParityQA** lock the night's color semantics (STEAM
|
||||
amber / VALUE green / red = STALE-or-miss only), mono data surfaces, and
|
||||
schedule-derived waiting copy. Keep them green.
|
||||
|
||||
## Active Skills
|
||||
- vyndr-voice (all user-facing output)
|
||||
- prop-analysis (grading methodology)
|
||||
|
||||
@@ -91,3 +91,48 @@ describe('QA.18 — auth gate integration', () => {
|
||||
expect(routes.isGatedRoute('/dashboard')).toBe(false); // free funnel stays open
|
||||
});
|
||||
});
|
||||
|
||||
// ── Session 60 (night2/H) — §2 design-pass locks for tonight's surfaces ─────
|
||||
// One meaning per color: green = edge/favorable, amber = caution/market
|
||||
// signal, red = miss/error. Data is mono. Glow stays on A-tier/chrome.
|
||||
|
||||
describe('QA.20 — movement + status color semantics (Phase 2.5 surfaces)', () => {
|
||||
it('STEAM is amber (caution: entry compressed), VALUE is green (edge)', () => {
|
||||
const strip = read('components/vyndr/StatStrip.tsx');
|
||||
expect(strip).toMatch(/steam \? 'var\(--amber/);
|
||||
expect(strip).toMatch(/: 'var\(--g-a/);
|
||||
});
|
||||
it('SYNC goes red ONLY at the STALE tier (error meaning), amber at 1.5x', () => {
|
||||
const live = read('components/vyndr/LiveLayer.tsx');
|
||||
expect(live).toContain("tier === 'stale' ? 'var(--miss)'");
|
||||
expect(live).toContain("tier === 'amber' ? 'var(--amber)'");
|
||||
});
|
||||
it('a revision renders the ORIGINAL grade struck through (public, never silent)', () => {
|
||||
expect(read('components/vyndr/StatStrip.tsx')).toContain("textDecoration: 'line-through'");
|
||||
expect(read('app/ledger/page.tsx')).toContain("textDecoration: 'line-through'");
|
||||
});
|
||||
it('lens difficulty: tougher = amber, softer = green — never inverted', () => {
|
||||
const panel = read('components/StreaksPanel.tsx');
|
||||
expect(panel).toMatch(/step up' \? 'var\(--amber[^']*' : [^:]*step down' \? 'var\(--g-a/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QA.21 — new data surfaces are mono', () => {
|
||||
it.each([
|
||||
'components/vyndr/PriorReads.tsx',
|
||||
'components/vyndr/PlayerStreaks.tsx',
|
||||
'components/vyndr/ModelRecord.tsx',
|
||||
])('%s renders its data rows in mono', (rel) => {
|
||||
expect(read(rel)).toContain('className="mono"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('QA.22 — honest waiting copy everywhere (no passive mystery)', () => {
|
||||
it.each([
|
||||
'components/vyndr/StatStrip.tsx',
|
||||
'app/dashboard/page.tsx',
|
||||
'components/ExploreHub.tsx',
|
||||
])('%s derives its waiting state from the real schedule', (rel) => {
|
||||
expect(read(rel)).toContain('nextRunLabelET');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { playerHref } from '@/lib/playerHref';
|
||||
import StreaksPanel from '@/components/StreaksPanel';
|
||||
import HotListPanel from '@/components/HotListPanel';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { nextRunLabelET } from '@/lib/pipelineSchedule';
|
||||
|
||||
interface Leader {
|
||||
player: string;
|
||||
@@ -105,7 +106,7 @@ export default function ExploreHub() {
|
||||
{state === 'error' && <div className="mono" style={{ padding: '30px 16px', textAlign: 'center', fontSize: 12, color: 'var(--miss)' }}>Could not load the leaderboard. Try again.</div>}
|
||||
{state === 'ready' && rows.length === 0 && (
|
||||
<div className="mono" style={{ padding: '30px 16px', textAlign: 'center', fontSize: 12, color: 'var(--text-2)' }}>
|
||||
No graded props for {sport.toUpperCase()} yet — check back when tonight's slate posts.
|
||||
No graded props for {sport.toUpperCase()} yet. Grades post {nextRunLabelET() || 'on the next pipeline run'}.
|
||||
</div>
|
||||
)}
|
||||
{state === 'ready' && rows.map((r, i) => (
|
||||
|
||||
Reference in New Issue
Block a user