Un-claim CLV on the public profile; un-fabricate player-page FORM
Two Truth-Law fixes found by auditing the product logged-out. FIX 1 — /u/[handle] claimed a "CLV-verified record" with "closing-line value included" while ZERO closing-line value renders there. Verified live: GET /api/profiles/vyndr returns beat_close_pct null (gated behind CLV_CAPTURE_RELIABLE, unset while C4 is open). Eight instances found — two of them (the OG + portrait "CLV-VERIFIED RECORD · 30D" eyebrows) only by the post-removal residual sweep; two more printed the claim in exactly the no-record branch. Copy now describes what the page shows. The gated CLV-VERIFIED badge and the BEAT CLOSE figure are removed from the public profile, OG card and portrait card. DISPLAY ONLY: beat_close_pct, clvCaptureReliable() and the whole CLV data path are untouched, and the earned directional badge stays Analyst+Desk. The claim returns when CLV genuinely renders here. Also fixes the doubled "· VYNDR · VYNDR" title (layout's '%s · VYNDR' template already supplies the suffix); verified on composed output by serving the build and reading the real HTML, not on source. FIX 2 — the player page's FORM was `70 + 4 × (count of tonight's graded props)`. Nothing on the HTTP path ever sets stats.form, so that fallback WAS the live number: Josh Bell's "74" is 70 + 4×1 prop, confirmed against his live payload. MATCHUP was gradeFromForm(that number), with a hardcoded 'B' on the no-archetype branch — both fabricated letters with no opponent input on the path. Systemic: buildIntel is the unconditional path for every player and sport. FORM and MATCHUP now render "—" (kind 'plain', so no bar width or colour is computed off a null). gradeFromForm is deleted and the prop count is no longer passed into buildIntel. computeFormScore's hardcoded 75 now returns undefined. Induced across MLB/NBA/WNBA: all render cleanly, and real values (USAGE 3.6 AB/G, REST B2B) still render. Neither form value feeds the grade — engine1 reads raw l5_avg/l20_avg against the line and never a form key; buildIntelFields decorates the already-graded object. Grade inputs are byte-identical. Held (needs a per-sport headline-stat design call): a real player-level form metric + label disambiguation. Tests 3491 passed / 289 suites, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
+100
-1
@@ -1,5 +1,5 @@
|
|||||||
# VYNDR — STATE OF THE WORLD
|
# VYNDR — STATE OF THE WORLD
|
||||||
### As of `7ea0af2` (main, DEPLOYED + fingerprinted live), 2026-07-20. This file opens every future session. **Start with the CURRENT STATUS + OPEN ITEMS block below.**
|
### As of `HEAD` on main (DEPLOYED + fingerprinted live), 2026-07-20. This file opens every future session. **Start with the CURRENT STATUS + OPEN ITEMS block below.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -131,6 +131,105 @@ exist locally; harmless, the data restores completely.
|
|||||||
`specs/snapshot-retention.md` (Phase 2 report + schema) ·
|
`specs/snapshot-retention.md` (Phase 2 report + schema) ·
|
||||||
`specs/audit-data/grade-collapse.md` · `specs/audit-data/gate-simulation.md`.
|
`specs/audit-data/grade-collapse.md` · `specs/audit-data/gate-simulation.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🟢 SESSION 65 — TWO TRUTH-LAW FIXES (2026-07-20, from Chrome's logged-out audit)
|
||||||
|
Both found by auditing the product as a STRANGER sees it. Neither touches the
|
||||||
|
triplet build; push scoring untouched.
|
||||||
|
|
||||||
|
## Doc drift corrected in this session
|
||||||
|
The header of this file was stale at `7ea0af2` and this file carried **no record
|
||||||
|
of the directional-CLV session** (`dcdad60` → `ca9ca34`: `src/services/
|
||||||
|
directionalClv.js`, the Analyst+Desk server-gated `ClvBadge`, and removal of the
|
||||||
|
legacy C4 CLV chip from five surfaces). `BUILD-STATE.md` is stale to April 2026 —
|
||||||
|
**`specs/STATE.md` is the authoritative state file**; treat BUILD-STATE as archive.
|
||||||
|
|
||||||
|
## FIX 1 — "CLV-VERIFIED" UN-CLAIMED ON THE PUBLIC PROFILE (copy + 2 render blocks)
|
||||||
|
`/u/[handle]` was titled **"CLV-verified record"** with the subhead **"closing-line
|
||||||
|
value included"** while **zero** closing-line value renders there. Verified live,
|
||||||
|
not assumed: `GET /api/profiles/vyndr` → `beat_close_pct: null` (gated behind
|
||||||
|
`clvCaptureReliable()` / `CLV_CAPTURE_RELIABLE`, which is unset while C4 is open).
|
||||||
|
A verification claim with nothing verified shown is an unearned claim, and it
|
||||||
|
leaked the standing CLV hold onto a surface that had never been audited.
|
||||||
|
|
||||||
|
**EIGHT instances found — two of them ONLY by the post-removal residual sweep:**
|
||||||
|
|
||||||
|
| # | Where | Was |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `u/[handle]/page.tsx` | `<title>` / `og:title` / `twitter:title` — "CLV-verified record" |
|
||||||
|
| 2 | `u/[handle]/page.tsx` | meta + og + twitter description — "closing-line value included" |
|
||||||
|
| 3 | `u/[handle]/opengraph-image.tsx` | no-record fallback tagline |
|
||||||
|
| 4 | `u/[handle]/portrait/route.tsx` | no-record fallback tagline |
|
||||||
|
| 5 | `PublicProfile.tsx` | house subhead — "closing-line value included" |
|
||||||
|
| 6 | `PublicProfile.tsx` | `✓ CLV-VERIFIED` badge (gated, dark, one env flag from re-claiming) |
|
||||||
|
| **7** | `opengraph-image.tsx` | **eyebrow `CLV-VERIFIED RECORD · 30D`** — missed in the first report |
|
||||||
|
| **8** | `portrait/route.tsx` | **same eyebrow** — missed in the first report |
|
||||||
|
|
||||||
|
Instances 3 + 4 printed the verification claim **exactly when there is no record
|
||||||
|
at all** (the `rec == null` branch), i.e. the claim was loudest where the data was
|
||||||
|
most absent.
|
||||||
|
|
||||||
|
**Applied:** honest copy on 1–5 + 7–8 (settled record · wins and misses · nothing
|
||||||
|
curated); the badge (6a) and the `BEAT CLOSE` figure (6b) REMOVED from the public
|
||||||
|
profile, OG card and portrait card. **DISPLAY ONLY — `beat_close_pct`,
|
||||||
|
`clvCaptureReliable()`, `closing_captures` and the whole CLV data path are
|
||||||
|
untouched**, and the earned directional badge stays Analyst+Desk (never public).
|
||||||
|
The claim returns here when CLV genuinely renders here.
|
||||||
|
|
||||||
|
**Title double-append fixed and VERIFIED ON COMPOSED OUTPUT, not source:** the page
|
||||||
|
title carried its own `· VYNDR` while `layout.tsx`'s metadata template already
|
||||||
|
appends `%s · VYNDR` → live was `…@vyndr · VYNDR · VYNDR`. Built the app, served
|
||||||
|
it, and read the real HTML: `<title>Settled record — @vyndr · VYNDR</title>` —
|
||||||
|
exactly one suffix. Note `openGraph`/`twitter` titles **bypass** the template, so
|
||||||
|
they carry the brand suffix explicitly.
|
||||||
|
|
||||||
|
Both share images re-rendered locally (200 / image/png) and eyeballed: no overflow,
|
||||||
|
no clip, no residual CLV scaffold.
|
||||||
|
|
||||||
|
## FIX 2 — FORM WAS FABRICATED ON THE PLAYER PAGE (un-fabricate; steps 2+3 HELD)
|
||||||
|
Chrome saw Josh Bell as **FORM 74** (player page) and **Form 63** (generated read)
|
||||||
|
in one session. Traced both. **Neither feeds the grade** — proven, not assumed:
|
||||||
|
`engine1.gradeProp` runs at `analyzeViaEngine1.js:446`, `buildIntelFields` (which
|
||||||
|
computes form) decorates the already-graded object at `:468`; `engine1.js` never
|
||||||
|
reads a `form` key (only raw `l5_avg`/`l20_avg` **against the line**), and nothing
|
||||||
|
in `src/` reads `result.form`. Grade inputs are byte-identical before/after.
|
||||||
|
|
||||||
|
- **63 is real** — `70 + (l5_avg / l20_avg − 1) × 60`, per-prop, per-stat.
|
||||||
|
- **74 was manufactured** — `70 + 4 × (count of tonight's graded props)`, capped
|
||||||
|
92. **Nothing on the HTTP path ever sets `stats.form`**, so that fallback WAS
|
||||||
|
the live value. Confirmed empirically: Bell's payload carries `activeProps`
|
||||||
|
length **1** → 70 + 4 = **74**, exact.
|
||||||
|
- It **poisoned a second tile**: `MATCHUP` was `gradeFromForm(form)` — Bell's
|
||||||
|
"B" was `gradeFromForm(74)`, from the prop count, with **no opponent input on
|
||||||
|
that path at all**; the no-archetype branch hardcoded `'B'`. Both fabricated.
|
||||||
|
- **SYSTEMIC, not Bell-specific** — `buildIntel` is the unconditional path for
|
||||||
|
every player and every sport.
|
||||||
|
|
||||||
|
**Applied (absent renders absent):** FORM and MATCHUP now emit `kind:'plain'`
|
||||||
|
with `'—'` when there is no real value — `'plain'` deliberately, so the page
|
||||||
|
draws no progress bar and computes no width/colour off a null. `gradeFromForm`
|
||||||
|
deleted; the prop count is no longer passed into `buildIntel` at all.
|
||||||
|
`computeFormScore`'s hardcoded **75** (returned whenever the baseline was missing
|
||||||
|
or zero) now returns `undefined` → the card's self-hiding intel section drops the
|
||||||
|
row. Induced live across **MLB / NBA / WNBA**: all render `—` cleanly; real values
|
||||||
|
(`USAGE 3.6 AB/G`, `REST B2B`) still render — the fix is surgical to fabrications.
|
||||||
|
|
||||||
|
**HELD (needs a per-sport headline-stat design call, deliberately NOT built):** a
|
||||||
|
real player-level form metric + label disambiguation (page-level vs per-stat). It
|
||||||
|
lands as `stats.form` / `stats.matchup` and lights these tiles straight back up.
|
||||||
|
|
||||||
|
### Audit of the same file (order item 10) — reported, NOT changed
|
||||||
|
`toNum()` (`playerIntelService.js:18`) returns **0** for absent, feeding
|
||||||
|
`classifierInput`: `k_rate` (`pa > 0 ? … : 0`), `ip_per_start`, `k9`, `saves`.
|
||||||
|
These are **classifier inputs, not display** — a 0 there can misclassify an
|
||||||
|
archetype (0% K-rate reads as elite contact). Same family as the `Number(null)
|
||||||
|
=== 0` rule. `mlbLast10Rows` renders `${st.hits ?? 0}-${st.atBats ?? 0}`, which
|
||||||
|
prints a real-looking `0-0` line if a game-log row lacks its stat object.
|
||||||
|
**Ambiguous — changing classifier inputs shifts archetypes, so it needs its own
|
||||||
|
order.** Display rows elsewhere (`mlbSeasonRows`, ESPN season rows) correctly use
|
||||||
|
`?? '—'`.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🟢 MLB opp_rank_stat LIVE — computed, CONSUMED, retained (2026-07-20 16:51 UTC)
|
## 🟢 MLB opp_rank_stat LIVE — computed, CONSUMED, retained (2026-07-20 16:51 UTC)
|
||||||
|
|||||||
@@ -327,7 +327,11 @@ function computeFormScore(features = {}) {
|
|||||||
if (!Number.isFinite(l5)) return undefined;
|
if (!Number.isFinite(l5)) return undefined;
|
||||||
const base = Number.isFinite(features.l20_avg) ? features.l20_avg
|
const base = Number.isFinite(features.l20_avg) ? features.l20_avg
|
||||||
: Number.isFinite(features.l10_avg) ? features.l10_avg : null;
|
: Number.isFinite(features.l10_avg) ? features.l10_avg : null;
|
||||||
if (base == null || base === 0) return 75;
|
// Session 65 — was `return 75`: a hardcoded constant rendered under the FORM
|
||||||
|
// label whenever the baseline was missing or zero. With no baseline there is
|
||||||
|
// no recent-vs-baseline ratio, so the field is absent and the card's
|
||||||
|
// self-hiding intel section drops the row. Absent renders absent.
|
||||||
|
if (base == null || base === 0) return undefined;
|
||||||
const ratio = l5 / base;
|
const ratio = l5 / base;
|
||||||
return Math.round(Math.max(40, Math.min(99, 70 + (ratio - 1) * 60)));
|
return Math.round(Math.max(40, Math.min(99, 70 + (ratio - 1) * 60)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,30 +211,46 @@ function mlbProfileIntel(res) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Derive the VYNDR Intelligence metric row from whatever we have. */
|
/**
|
||||||
function buildIntel(stats, arch, propCount) {
|
* Derive the VYNDR Intelligence metric row from whatever we have.
|
||||||
|
*
|
||||||
|
* Session 65 — UN-FABRICATE (Truth Law: if it renders a number, it comes from
|
||||||
|
* real data or it does not render).
|
||||||
|
* - FORM used to fall back to `70 + 4 × (count of tonight's graded props)`,
|
||||||
|
* capped at 92. Nothing on the HTTP path ever sets `stats.form`, so that
|
||||||
|
* fallback WAS the live value: Josh Bell's "74" was 70 + 4×1 graded prop —
|
||||||
|
* a number carrying zero information about the player. Absent now renders
|
||||||
|
* absent.
|
||||||
|
* - MATCHUP used to be `gradeFromForm(form)` (i.e. the same prop count) with a
|
||||||
|
* hardcoded 'B' when no archetype resolved. There is no opponent input on
|
||||||
|
* this path at all, so BOTH branches were fabricated letters. Absent now
|
||||||
|
* renders absent.
|
||||||
|
* A real form metric is a separate, held design call (per-sport headline stat);
|
||||||
|
* it lands as `stats.form` / `stats.matchup` and lights these tiles back up.
|
||||||
|
* Tiles emit kind 'plain' when absent so the page prints "—" and draws no
|
||||||
|
* progress bar — no math, colour or bar width is computed off a null.
|
||||||
|
*/
|
||||||
|
function buildIntel(stats, arch) {
|
||||||
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
||||||
// Form: lean on last-10 vs season if provided, else a neutral baseline that
|
const form = stats.form != null && Number.isFinite(Number(stats.form))
|
||||||
// scales gently with the strongest graded prop's confidence proxy.
|
? clamp(Math.round(Number(stats.form)), 0, 100)
|
||||||
const form = stats.form != null ? clamp(Math.round(stats.form), 0, 100) : 70 + clamp(propCount * 4, 0, 22);
|
: null;
|
||||||
// Session 48 — prefer a preformatted usage string (e.g. "3.6 AB/G"); rest
|
// Session 48 — prefer a preformatted usage string (e.g. "3.6 AB/G"); rest
|
||||||
// shows the real value or "—" (no more bogus "+0%").
|
// shows the real value or "—" (no more bogus "+0%").
|
||||||
const usage = stats.usage || (stats.usg != null ? `${stats.usg}%` : stats.k9 != null ? `${stats.k9} K/9` : '—');
|
const usage = stats.usage || (stats.usg != null ? `${stats.usg}%` : stats.k9 != null ? `${stats.k9} K/9` : '—');
|
||||||
|
const matchup = typeof stats.matchup === 'string' && stats.matchup ? stats.matchup : null;
|
||||||
return [
|
return [
|
||||||
{ label: 'FORM', kind: 'form', value: String(form), score: `${clamp(form, 0, 100)}%`, color: form >= 85 ? '#00ffb8' : form >= 70 ? '#00D4A0' : '#FFB347' },
|
form != null
|
||||||
|
? { label: 'FORM', kind: 'form', value: String(form), score: `${form}%`, color: form >= 85 ? '#00ffb8' : form >= 70 ? '#00D4A0' : '#FFB347' }
|
||||||
|
: { label: 'FORM', kind: 'plain', value: '—', color: '#7A7A8E' },
|
||||||
{ label: 'USAGE', kind: 'plain', value: usage, color: '#e8e8f0' },
|
{ label: 'USAGE', kind: 'plain', value: usage, color: '#e8e8f0' },
|
||||||
{ label: 'MATCHUP', kind: 'grade', value: arch.primary ? gradeFromForm(form) : 'B', color: '#00D4A0' },
|
matchup
|
||||||
{ label: 'REST', kind: 'plain', value: stats.rest || '—', color: '#00D4A0' },
|
? { label: 'MATCHUP', kind: 'grade', value: matchup, color: '#00D4A0' }
|
||||||
|
: { label: 'MATCHUP', kind: 'plain', value: '—', color: '#7A7A8E' },
|
||||||
|
{ label: 'REST', kind: 'plain', value: stats.rest || '—', color: stats.rest ? '#00D4A0' : '#7A7A8E' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function gradeFromForm(form) {
|
|
||||||
if (form >= 90) return 'A';
|
|
||||||
if (form >= 80) return 'B+';
|
|
||||||
if (form >= 70) return 'B';
|
|
||||||
return 'C';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the full player intelligence payload.
|
* Build the full player intelligence payload.
|
||||||
* opts: { cacheGet, stats, season, last10, splits, gradeHistory, injury, team }
|
* opts: { cacheGet, stats, season, last10, splits, gradeHistory, injury, team }
|
||||||
@@ -289,7 +305,9 @@ async function getPlayerIntel(name, sport, opts = {}) {
|
|||||||
splits: resolved.splits || opts.splits || [],
|
splits: resolved.splits || opts.splits || [],
|
||||||
gradeHistory: opts.gradeHistory || [],
|
gradeHistory: opts.gradeHistory || [],
|
||||||
activeProps,
|
activeProps,
|
||||||
intel: buildIntel(realStats, archetype, props.length),
|
// Session 65 — prop count is deliberately NOT passed any more: it used to
|
||||||
|
// manufacture the FORM number. Intel is a function of real stats only.
|
||||||
|
intel: buildIntel(realStats, archetype),
|
||||||
injury: opts.injury || null,
|
injury: opts.injury || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ describe('GradeResultCard — the letter is hero, edge is sign-colored', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── 4. /u public profile — the shareable record billboard (#4) ────────────
|
// ── 4. /u public profile — the shareable record billboard (#4) ────────────
|
||||||
describe('PublicProfile — the record hero + CLV-verified badge', () => {
|
describe('PublicProfile — the record hero (Session 65: no CLV claim)', () => {
|
||||||
const src = read('app/u/[handle]/PublicProfile.tsx');
|
const src = read('app/u/[handle]/PublicProfile.tsx');
|
||||||
|
|
||||||
test('the hit rate is the bold hero figure (large mono tabular)', () => {
|
test('the hit rate is the bold hero figure (large mono tabular)', () => {
|
||||||
@@ -141,14 +141,15 @@ describe('PublicProfile — the record hero + CLV-verified badge', () => {
|
|||||||
expect(src).toContain('HIT RATE');
|
expect(src).toContain('HIT RATE');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('beat-close is the secondary bold figure', () => {
|
// Session 65 — UN-CLAIM. The beat-close figure and the ✓ CLV-VERIFIED badge
|
||||||
expect(src).toContain('{agg.beat_close_pct}%');
|
// are GONE from this public surface: both read beat_close_pct, derived from
|
||||||
expect(src).toContain('BEAT CLOSE');
|
// the legacy overwritable closing_line (C4 open). Display removal only — the
|
||||||
});
|
// aggregate and its CLV_CAPTURE_RELIABLE gate in ledgerService are untouched.
|
||||||
|
test('no closing-line claim renders on the public record hero', () => {
|
||||||
test('CLV-VERIFIED badge is HONEST — only when closing value is tracked', () => {
|
expect(src).not.toContain('{agg.beat_close_pct}%');
|
||||||
expect(src).toContain('✓ CLV-VERIFIED');
|
expect(src).not.toContain('BEAT CLOSE');
|
||||||
expect(src).toMatch(/clvVerified = Boolean\(ready && agg && agg\.beat_close_pct != null\)/);
|
expect(src).not.toContain('CLV-VERIFIED');
|
||||||
|
expect(src).not.toMatch(/clvVerified/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('still honors the n-gate: RECORD BUILDING under min_sample', () => {
|
test('still honors the n-gate: RECORD BUILDING under min_sample', () => {
|
||||||
@@ -170,13 +171,15 @@ describe('/u opengraph-image — real record at 1200x630 social crop', () => {
|
|||||||
expect(src).toMatch(/size = \{ width: 1200, height: 630 \}/);
|
expect(src).toMatch(/size = \{ width: 1200, height: 630 \}/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders the real hit% / beat-close% when the record is published', () => {
|
test('renders the real hit% when the record is published, and claims no CLV', () => {
|
||||||
expect(src).toContain('{rec.hit_pct}%');
|
expect(src).toContain('{rec.hit_pct}%');
|
||||||
expect(src).toContain('{rec.beat_close_pct}%');
|
// Session 65 — beat-close% + the CLV-VERIFIED eyebrow are un-claimed here.
|
||||||
expect(src).toContain('CLV-VERIFIED RECORD');
|
expect(src).not.toContain('{rec.beat_close_pct}%');
|
||||||
|
expect(src).not.toMatch(/CLV-VERIFIED|CLV-verified/);
|
||||||
|
expect(src).toContain('SETTLED RECORD · 30D');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('falls back gracefully to the tagline when there is no record', () => {
|
test('falls back gracefully to an honest tagline when there is no record', () => {
|
||||||
expect(src).toContain('every settled read · misses included');
|
expect(src).toContain('Every settled read · wins and misses · nothing curated');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ describe('computeFormScore', () => {
|
|||||||
it('undefined when there is no recent average', () => {
|
it('undefined when there is no recent average', () => {
|
||||||
expect(computeFormScore({})).toBeUndefined();
|
expect(computeFormScore({})).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
// Session 65 — UN-FABRICATE. This branch used to `return 75`: a hardcoded
|
||||||
|
// constant rendered under the FORM label whenever the baseline was missing
|
||||||
|
// or zero. With no baseline there is no ratio, so the field is absent and
|
||||||
|
// the card's self-hiding intel section drops the row.
|
||||||
|
it('undefined (never a hardcoded 75) when the baseline is missing or zero', () => {
|
||||||
|
expect(computeFormScore({ l5_avg: 12 })).toBeUndefined();
|
||||||
|
expect(computeFormScore({ l5_avg: 12, l20_avg: 0 })).toBeUndefined();
|
||||||
|
expect(computeFormScore({ l5_avg: 12, l20_avg: 0, l10_avg: 0 })).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matchupGradeFromRank', () => {
|
describe('matchupGradeFromRank', () => {
|
||||||
|
|||||||
@@ -89,3 +89,60 @@ describe('getLeaders', () => {
|
|||||||
expect(await svc.getLeaders('nba', { cacheGet: async () => null })).toEqual([]);
|
expect(await svc.getLeaders('nba', { cacheGet: async () => null })).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Session 65 — UN-FABRICATE (Truth Law) ────────────────────────────────
|
||||||
|
// FORM used to be `70 + 4 × (count of tonight's graded props)` whenever
|
||||||
|
// `stats.form` was absent — and nothing on the HTTP path ever sets it, so that
|
||||||
|
// fallback WAS the live number (Josh Bell's "74" = 70 + 4×1 prop). MATCHUP was
|
||||||
|
// `gradeFromForm(that number)`, with a hardcoded 'B' when no archetype
|
||||||
|
// resolved: both branches fabricated a letter with zero opponent input.
|
||||||
|
describe('buildIntel — absent renders absent, never a manufactured number', () => {
|
||||||
|
const { buildIntel } = svc._internals;
|
||||||
|
const arch = { primary: { name: 'BOMBER' } };
|
||||||
|
|
||||||
|
it('renders FORM as "—" with no bar when no real form value exists', () => {
|
||||||
|
const form = buildIntel({}, arch).find((m) => m.label === 'FORM');
|
||||||
|
expect(form.value).toBe('—');
|
||||||
|
expect(form.kind).toBe('plain'); // 'plain' draws no progress bar → no width math on a null
|
||||||
|
expect(form.score).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders MATCHUP as "—", never a grade letter, with no opponent input', () => {
|
||||||
|
const m = buildIntel({}, arch).find((x) => x.label === 'MATCHUP');
|
||||||
|
expect(m.value).toBe('—');
|
||||||
|
expect(m.kind).toBe('plain');
|
||||||
|
// and not merely because the archetype was missing — the old code
|
||||||
|
// hardcoded 'B' on that branch too.
|
||||||
|
const noArch = buildIntel({}, { primary: null }).find((x) => x.label === 'MATCHUP');
|
||||||
|
expect(noArch.value).toBe('—');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the prop count can NEVER move FORM (the fabrication that shipped)', async () => {
|
||||||
|
const mk = (n) => ({
|
||||||
|
grades: Array.from({ length: n }, (_, i) => ({
|
||||||
|
player: 'Josh Bell', stat_type: `stat_${i}`, line: 0.5, direction: 'over', grade: 'B', confidence: 57,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
const formFor = async (n) => {
|
||||||
|
const r = await svc.getPlayerIntel('Josh Bell', 'mlb', { cacheGet: cacheWith(mk(n)), stats: {} });
|
||||||
|
return r.intel.find((m) => m.label === 'FORM').value;
|
||||||
|
};
|
||||||
|
expect(await formFor(1)).toBe('—'); // was '74'
|
||||||
|
expect(await formFor(5)).toBe('—'); // was '90'
|
||||||
|
expect(await formFor(0)).toBe('—'); // was '70'
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still renders a REAL form value when one is supplied (un-claim, not un-build)', () => {
|
||||||
|
const form = buildIntel({ form: 63 }, arch).find((m) => m.label === 'FORM');
|
||||||
|
expect(form.value).toBe('63');
|
||||||
|
expect(form.kind).toBe('form');
|
||||||
|
expect(form.score).toBe('63%');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('usage and rest keep their honest absent states', () => {
|
||||||
|
const rows = buildIntel({}, arch);
|
||||||
|
expect(rows.find((m) => m.label === 'USAGE').value).toBe('—');
|
||||||
|
expect(rows.find((m) => m.label === 'REST').value).toBe('—');
|
||||||
|
expect(buildIntel({ usage: '3.6 AB/G', rest: 'B2B' }, arch).find((m) => m.label === 'USAGE').value).toBe('3.6 AB/G');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -25,9 +25,17 @@ describe('/u routing — the share surface stays public', () => {
|
|||||||
|
|
||||||
describe('/u/[handle] server shell', () => {
|
describe('/u/[handle] server shell', () => {
|
||||||
const src = read('app/u/[handle]/page.tsx');
|
const src = read('app/u/[handle]/page.tsx');
|
||||||
it('carries the CLV-verified metadata title', () => {
|
// Session 65 — UN-CLAIM. The metadata claimed a CLV verification that never
|
||||||
expect(src).toContain('CLV-verified record — @');
|
// renders on this surface (beat_close_pct is null until C4 is fixed). Copy
|
||||||
expect(src).toContain('· VYNDR');
|
// must describe exactly what the page shows and imply no closing-line value.
|
||||||
|
it('carries honest settled-record metadata and claims no CLV', () => {
|
||||||
|
expect(src).toContain('Settled record — @');
|
||||||
|
expect(src).toContain('wins and misses');
|
||||||
|
expect(src).not.toMatch(/CLV-verified|closing-line value included/);
|
||||||
|
});
|
||||||
|
it('does NOT repeat the layout title template suffix (avoids "· VYNDR · VYNDR")', () => {
|
||||||
|
// layout.tsx metadata template is '%s · VYNDR' — the page title must not add its own.
|
||||||
|
expect(src).not.toMatch(/const title = `[^`]*· VYNDR`/);
|
||||||
});
|
});
|
||||||
it('renders the client record component', () => {
|
it('renders the client record component', () => {
|
||||||
expect(src).toContain('PublicProfile');
|
expect(src).toContain('PublicProfile');
|
||||||
@@ -41,11 +49,13 @@ describe('/u/[handle] client record', () => {
|
|||||||
expect(src).toContain('RECORD BUILDING');
|
expect(src).toContain('RECORD BUILDING');
|
||||||
expect(src).toMatch(/settled >= minSample/);
|
expect(src).toMatch(/settled >= minSample/);
|
||||||
});
|
});
|
||||||
it('shows outcome + CLV chips on settled rows (ledger card patterns)', () => {
|
it('shows outcome chips on settled rows, and NO CLV claim (Session 65 un-claim)', () => {
|
||||||
expect(src).toContain('✓ HIT');
|
expect(src).toContain('✓ HIT');
|
||||||
expect(src).toContain('✕ MISS');
|
expect(src).toContain('✕ MISS');
|
||||||
expect(src).toContain('BEAT CLOSE');
|
// The BEAT CLOSE figure and the ✓ CLV-VERIFIED badge are removed from this
|
||||||
expect(src).toContain('clv_result');
|
// public surface — both derived from the legacy overwritable closing_line.
|
||||||
|
expect(src).not.toContain('BEAT CLOSE');
|
||||||
|
expect(src).not.toContain('CLV-VERIFIED');
|
||||||
});
|
});
|
||||||
it('states the uncurated contract and one not-found state for unknown AND unpublished', () => {
|
it('states the uncurated contract and one not-found state for unknown AND unpublished', () => {
|
||||||
expect(src).toContain('Nothing curated');
|
expect(src).toContain('Nothing curated');
|
||||||
@@ -63,7 +73,9 @@ describe('/u/[handle] OG image', () => {
|
|||||||
expect(og).toContain('width: 1200, height: 630');
|
expect(og).toContain('width: 1200, height: 630');
|
||||||
expect(og).toContain("contentType = 'image/png'");
|
expect(og).toContain("contentType = 'image/png'");
|
||||||
expect(og).toContain('PUBLIC LEDGER');
|
expect(og).toContain('PUBLIC LEDGER');
|
||||||
expect(og).toContain('misses included');
|
// Session 65 — the no-record fallback tagline, un-claimed (was
|
||||||
|
// "CLV-verified record · every settled read · misses included").
|
||||||
|
expect(og).toContain('Every settled read · wins and misses · nothing curated');
|
||||||
});
|
});
|
||||||
it('does NOT use the edge runtime (self-hosted standalone, S53 rule)', () => {
|
it('does NOT use the edge runtime (self-hosted standalone, S53 rule)', () => {
|
||||||
expect(og).not.toContain("runtime = 'edge'");
|
expect(og).not.toContain("runtime = 'edge'");
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -10,8 +10,9 @@ import { buildTierRows } from '@/lib/tierRecord';
|
|||||||
/**
|
/**
|
||||||
* PublicProfile (A1 Session 10) — the public ledger record for one handle.
|
* PublicProfile (A1 Session 10) — the public ledger record for one handle.
|
||||||
*
|
*
|
||||||
* DATA SEMANTICS: every row here is a REAL settled ledger entry — outcome,
|
* DATA SEMANTICS: every row here is a REAL settled ledger entry — outcome and
|
||||||
* actual value, closing-line value. Nothing curated: publishing shows the
|
* actual value. No closing-line value renders on this public surface (Session
|
||||||
|
* 65 un-claim). Nothing curated: publishing shows the
|
||||||
* ENTIRE settled record, misses included. The header NEVER renders a
|
* ENTIRE settled record, misses included. The header NEVER renders a
|
||||||
* percentage under min_sample (20) settles — it shows RECORD BUILDING.
|
* percentage under min_sample (20) settles — it shows RECORD BUILDING.
|
||||||
*
|
*
|
||||||
@@ -133,7 +134,7 @@ export default function PublicProfile({ handle }: { handle: string }) {
|
|||||||
</h1>
|
</h1>
|
||||||
<p style={{ color: 'var(--text-secondary)', fontSize: 15 }}>
|
<p style={{ color: 'var(--text-secondary)', fontSize: 15 }}>
|
||||||
{isHouse
|
{isHouse
|
||||||
? 'The VYNDR model’s public record. Every graded read, wins and misses, closing-line value included.'
|
? 'The VYNDR model’s public record. Every graded read, wins and misses. Nothing curated.'
|
||||||
: 'Every settled read. Wins and misses. Nothing curated.'}
|
: 'Every settled read. Wins and misses. Nothing curated.'}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
@@ -172,13 +173,21 @@ export default function PublicProfile({ handle }: { handle: string }) {
|
|||||||
/**
|
/**
|
||||||
* The record HERO (DS4 · billboard — DESIGN-SPEC Part 3 + Part 6 #4). This is
|
* The record HERO (DS4 · billboard — DESIGN-SPEC Part 3 + Part 6 #4). This is
|
||||||
* the shareable surface Kev pitches partners with. One bold figure among muted
|
* the shareable surface Kev pitches partners with. One bold figure among muted
|
||||||
* context: the HIT RATE as a large mono tabular number, beat-close as the
|
* context: the HIT RATE as a large mono tabular number, the raw record demoted.
|
||||||
* secondary bold figure, the raw record demoted. CLV-VERIFIED badge only when
|
* Session 65 removed the closing-value figure + verification badge — see the
|
||||||
* closing-line value is actually tracked (honest — never a decorative badge).
|
* un-claim note inside.
|
||||||
*/
|
*/
|
||||||
function RecordHeader({ agg, minSample }: { agg: ProfileAggregate | null; minSample: number }) {
|
function RecordHeader({ agg, minSample }: { agg: ProfileAggregate | null; minSample: number }) {
|
||||||
const ready = Boolean(agg && agg.settled >= minSample && agg.hit_pct != null);
|
const ready = Boolean(agg && agg.settled >= minSample && agg.hit_pct != null);
|
||||||
const clvVerified = Boolean(ready && agg && agg.beat_close_pct != null);
|
// Session 65 — UN-CLAIM. The closing-value verification badge and the
|
||||||
|
// secondary close-beat figure were REMOVED from this public surface. Both
|
||||||
|
// read `beat_close_pct`, derived from the legacy overwritable `closing_line`
|
||||||
|
// (C4 open). The badge was gated behind CLV_CAPTURE_RELIABLE — one env flag
|
||||||
|
// away from re-asserting verification on the same data Session 64 deleted
|
||||||
|
// from these rows as untrustworthy. Removal is DISPLAY ONLY: the aggregate,
|
||||||
|
// `clvCaptureReliable()` and the whole closing-value data path are untouched,
|
||||||
|
// and the earned directional badge stays Analyst+Desk (never public). The
|
||||||
|
// claim returns here when closing value genuinely renders here.
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="surface diagonal-cut"
|
className="surface diagonal-cut"
|
||||||
@@ -190,11 +199,6 @@ function RecordHeader({ agg, minSample }: { agg: ProfileAggregate | null; minSam
|
|||||||
<span className="mono" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', color: 'var(--text-tertiary)' }}>
|
<span className="mono" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', color: 'var(--text-tertiary)' }}>
|
||||||
RECORD · LAST 30D
|
RECORD · LAST 30D
|
||||||
</span>
|
</span>
|
||||||
{clvVerified && (
|
|
||||||
<span className="mono" style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: '0.08em', color: 'var(--g-a, #00D4A0)', border: '1px solid var(--g-a, #00D4A0)', borderRadius: 999, padding: '3px 10px', background: 'color-mix(in srgb, var(--g-a, #00D4A0) 12%, transparent)' }}>
|
|
||||||
✓ CLV-VERIFIED
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{/* HERO figures — hit rate is the largest thing on the page. */}
|
{/* HERO figures — hit rate is the largest thing on the page. */}
|
||||||
<div style={{ display: 'flex', gap: 40, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: 40, flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||||
@@ -206,16 +210,6 @@ function RecordHeader({ agg, minSample }: { agg: ProfileAggregate | null; minSam
|
|||||||
HIT RATE · {agg.hits}-{agg.misses}
|
HIT RATE · {agg.hits}-{agg.misses}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{agg.beat_close_pct != null && (
|
|
||||||
<div>
|
|
||||||
<div className="mono" style={{ fontSize: 40, fontWeight: 800, lineHeight: 0.95, letterSpacing: '-0.02em', color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>
|
|
||||||
{agg.beat_close_pct}%
|
|
||||||
</div>
|
|
||||||
<div className="mono" style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '0.12em', color: 'var(--text-tertiary)', marginTop: 4 }}>
|
|
||||||
BEAT CLOSE
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div style={{ marginLeft: 'auto' }}>
|
<div style={{ marginLeft: 'auto' }}>
|
||||||
<div className="mono" style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-tertiary)', fontVariantNumeric: 'tabular-nums' }}>
|
<div className="mono" style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-tertiary)', fontVariantNumeric: 'tabular-nums' }}>
|
||||||
{agg.pending} pending
|
{agg.pending} pending
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { ImageResponse } from 'next/og';
|
|||||||
|
|
||||||
// A1 Session 10 · elevated DS4 — every shared /u/:handle link unfurls as a
|
// A1 Session 10 · elevated DS4 — every shared /u/:handle link unfurls as a
|
||||||
// record card at social crop (1200×630). The billboard now carries the REAL
|
// record card at social crop (1200×630). The billboard now carries the REAL
|
||||||
// record (hit% + beat-close%) when the handle is published + past the n-gate,
|
// record (hit%) when the handle is published + past the n-gate (Session 65
|
||||||
|
// removed beat-close% — see the un-claim note below),
|
||||||
// so the timeline (customer #1) sees the proof, not just a tagline. Self-hosted
|
// so the timeline (customer #1) sees the proof, not just a tagline. Self-hosted
|
||||||
// standalone build → Node runtime (NOT edge; Session-53 rule).
|
// standalone build → Node runtime (NOT edge; Session-53 rule).
|
||||||
export const alt = 'VYNDR Public Ledger';
|
export const alt = 'VYNDR Public Ledger';
|
||||||
@@ -49,7 +50,7 @@ export default async function Image({ params }: { params: Promise<{ handle: stri
|
|||||||
<div style={{ display: 'flex', position: 'absolute', top: 0, left: 0, right: 0, height: 6, background: '#00D4A0' }} />
|
<div style={{ display: 'flex', position: 'absolute', top: 0, left: 0, right: 0, height: 6, background: '#00D4A0' }} />
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<div style={{ display: 'flex', fontSize: 26, letterSpacing: '0.22em', color: '#00D4A0', fontWeight: 700 }}>
|
<div style={{ display: 'flex', fontSize: 26, letterSpacing: '0.22em', color: '#00D4A0', fontWeight: 700 }}>
|
||||||
{rec ? (rec.house ? 'VYNDR MODEL · PUBLIC RECORD' : 'CLV-VERIFIED RECORD · 30D') : 'PUBLIC LEDGER'}
|
{rec ? (rec.house ? 'VYNDR MODEL · PUBLIC RECORD' : 'SETTLED RECORD · 30D') : 'PUBLIC LEDGER'}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', fontSize: 72, fontWeight: 900, letterSpacing: '-0.02em', marginTop: 14, color: '#FFFFFF' }}>
|
<div style={{ display: 'flex', fontSize: 72, fontWeight: 900, letterSpacing: '-0.02em', marginTop: 14, color: '#FFFFFF' }}>
|
||||||
{rec?.house ? 'VYNDR MODEL' : `@${h}`}
|
{rec?.house ? 'VYNDR MODEL' : `@${h}`}
|
||||||
@@ -60,16 +61,15 @@ export default async function Image({ params }: { params: Promise<{ handle: stri
|
|||||||
<div style={{ display: 'flex', fontSize: 128, fontWeight: 900, lineHeight: 1, color: '#00D4A0' }}>{rec.hit_pct}%</div>
|
<div style={{ display: 'flex', fontSize: 128, fontWeight: 900, lineHeight: 1, color: '#00D4A0' }}>{rec.hit_pct}%</div>
|
||||||
<div style={{ display: 'flex', fontSize: 22, letterSpacing: '0.14em', color: '#7A7A8E', marginTop: 8 }}>HIT RATE · {rec.hits}-{rec.misses}</div>
|
<div style={{ display: 'flex', fontSize: 22, letterSpacing: '0.14em', color: '#7A7A8E', marginTop: 8 }}>HIT RATE · {rec.hits}-{rec.misses}</div>
|
||||||
</div>
|
</div>
|
||||||
{rec.beat_close_pct != null && (
|
{/* Session 65 — the secondary close-beat figure was REMOVED from
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
this public share card. It rendered beat_close_pct, derived from
|
||||||
<div style={{ display: 'flex', fontSize: 92, fontWeight: 900, lineHeight: 1, color: '#FFFFFF' }}>{rec.beat_close_pct}%</div>
|
the legacy overwritable closing_line (C4 open: closing == locked
|
||||||
<div style={{ display: 'flex', fontSize: 22, letterSpacing: '0.14em', color: '#7A7A8E', marginTop: 8 }}>BEAT CLOSE</div>
|
on ~95% of rows). Display removal only — the aggregate + its gate
|
||||||
</div>
|
in ledgerService are untouched. */}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'flex', fontSize: 28, color: '#7A7A8E', marginTop: 20 }}>
|
<div style={{ display: 'flex', fontSize: 28, color: '#7A7A8E', marginTop: 20 }}>
|
||||||
CLV-verified record · every settled read · misses included
|
Every settled read · wins and misses · nothing curated
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,13 +12,23 @@ import PublicProfile from './PublicProfile';
|
|||||||
export async function generateMetadata({ params }: { params: Promise<{ handle: string }> }): Promise<Metadata> {
|
export async function generateMetadata({ params }: { params: Promise<{ handle: string }> }): Promise<Metadata> {
|
||||||
const { handle } = await params;
|
const { handle } = await params;
|
||||||
const h = decodeURIComponent(handle || '').slice(0, 20).toLowerCase();
|
const h = decodeURIComponent(handle || '').slice(0, 20).toLowerCase();
|
||||||
const title = `CLV-verified record — @${h} · VYNDR`;
|
// Session 65 — UN-CLAIM. The title/description previously asserted a
|
||||||
const description = `@${h}'s settled betting record on VYNDR — every read, every result, closing-line value included. Nothing curated, nothing deleted.`;
|
// closing-line-verified record while ZERO closing-line value
|
||||||
|
// renders on this surface (beat_close_pct is null until C4 is fixed and
|
||||||
|
// CLV_CAPTURE_RELIABLE flips; the earned directional badge is Analyst+Desk
|
||||||
|
// and never reaches this public page). Copy now describes exactly what the
|
||||||
|
// page shows. The claim returns, earned, when CLV genuinely renders here.
|
||||||
|
// NOTE: layout.tsx's metadata template appends " · VYNDR" — do not repeat it.
|
||||||
|
const title = `Settled record — @${h}`;
|
||||||
|
const description = `@${h}'s settled betting record on VYNDR — every graded read, every result, wins and misses. Nothing curated, nothing deleted.`;
|
||||||
|
// openGraph/twitter titles bypass the layout template, so they carry the
|
||||||
|
// brand suffix explicitly; the page <title> must not (it would double it).
|
||||||
|
const socialTitle = `${title} · VYNDR`;
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
openGraph: { title, description },
|
openGraph: { title: socialTitle, description },
|
||||||
twitter: { card: 'summary_large_image', title, description },
|
twitter: { card: 'summary_large_image', title: socialTitle, description },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export async function GET(
|
|||||||
const h = decodeURIComponent(handle || '').slice(0, 20).toLowerCase() || 'handle';
|
const h = decodeURIComponent(handle || '').slice(0, 20).toLowerCase() || 'handle';
|
||||||
const rec = await fetchRecord(h);
|
const rec = await fetchRecord(h);
|
||||||
const heading = rec?.house ? 'VYNDR MODEL' : `@${h}`;
|
const heading = rec?.house ? 'VYNDR MODEL' : `@${h}`;
|
||||||
const eyebrow = rec ? (rec.house ? 'VYNDR MODEL · PUBLIC RECORD' : 'CLV-VERIFIED RECORD · 30D') : 'PUBLIC LEDGER';
|
const eyebrow = rec ? (rec.house ? 'VYNDR MODEL · PUBLIC RECORD' : 'SETTLED RECORD · 30D') : 'PUBLIC LEDGER';
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
@@ -88,16 +88,13 @@ export async function GET(
|
|||||||
<div style={{ display: 'flex', fontSize: 240, fontWeight: 900, lineHeight: 1, color: '#00D4A0' }}>{rec.hit_pct}%</div>
|
<div style={{ display: 'flex', fontSize: 240, fontWeight: 900, lineHeight: 1, color: '#00D4A0' }}>{rec.hit_pct}%</div>
|
||||||
<div style={{ display: 'flex', fontSize: 30, letterSpacing: '0.14em', color: '#7A7A8E', marginTop: 12 }}>HIT RATE · {rec.hits}-{rec.misses}</div>
|
<div style={{ display: 'flex', fontSize: 30, letterSpacing: '0.14em', color: '#7A7A8E', marginTop: 12 }}>HIT RATE · {rec.hits}-{rec.misses}</div>
|
||||||
</div>
|
</div>
|
||||||
{rec.beat_close_pct != null && (
|
{/* Session 65 — the close-beat figure was removed from the public
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
portrait card (legacy C4 closing_line derivation). Display
|
||||||
<div style={{ display: 'flex', fontSize: 128, fontWeight: 900, lineHeight: 1, color: '#FFFFFF' }}>{rec.beat_close_pct}%</div>
|
removal only; the data path is untouched. */}
|
||||||
<div style={{ display: 'flex', fontSize: 30, letterSpacing: '0.14em', color: '#7A7A8E', marginTop: 12 }}>BEAT CLOSE</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'flex', fontSize: 40, color: '#7A7A8E', lineHeight: 1.4 }}>
|
<div style={{ display: 'flex', fontSize: 40, color: '#7A7A8E', lineHeight: 1.4 }}>
|
||||||
CLV-verified record · every settled read · misses included
|
Every settled read · wins and misses · nothing curated
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user