diff --git a/tests/unit/clvLegacyRemoval.test.js b/tests/unit/clvLegacyRemoval.test.js
new file mode 100644
index 0000000..0ba2236
--- /dev/null
+++ b/tests/unit/clvLegacyRemoval.test.js
@@ -0,0 +1,90 @@
+/**
+ * Session 64 — the legacy C4 CLV display is GONE from every surface.
+ *
+ * TRUTH FIX, not a placement. row.clv/clv_result derive from the overwritable
+ * closing_line: 637/699 rows had closing == locked, so 522 of the 578 rendered
+ * "flat"s encoded a CAPTURE FAILURE as a held line. That number was live on
+ * FIVE surfaces, two of them public.
+ *
+ * These lock the removal so it cannot quietly return, and lock the distinction
+ * between the two CLV lineages.
+ */
+const fs = require('fs');
+const path = require('path');
+const read = (f) => fs.readFileSync(path.join(__dirname, '..', '..', f), 'utf8');
+
+const SURFACES = {
+ 'ledger card (authed)': 'web/src/app/ledger/page.tsx',
+ 'public profile (share link)': 'web/src/app/u/[handle]/PublicProfile.tsx',
+ 'dashboard': 'web/src/app/dashboard/page.tsx',
+ 'board / Slate': 'web/src/components/Slate.tsx',
+};
+
+// Comments explaining the removal legitimately mention the old names.
+const stripComments = (src) => src
+ .replace(/\/\*[\s\S]*?\*\//g, '')
+ .split('\n').filter((l) => !l.trim().startsWith('//')).join('\n');
+
+describe('LEGACY C4 CLV — removed from every surface', () => {
+ for (const [name, file] of Object.entries(SURFACES)) {
+ test(`${name} renders no legacy CLV`, () => {
+ const code = stripComments(read(file));
+ expect(code).not.toMatch(/clv_result\.toUpperCase/);
+ expect(code).not.toMatch(/CLV \{row\.clv/);
+ expect(code).not.toMatch(/CLV BEAT/);
+ });
+ }
+
+ test('the ClvChip component itself no longer exists', () => {
+ expect(stripComments(read(SURFACES['ledger card (authed)']))).not.toMatch(/function ClvChip/);
+ expect(stripComments(read(SURFACES['public profile (share link)']))).not.toMatch(/ClvChip/);
+ });
+});
+
+describe('THE TWO LINEAGES MUST NEVER CROSS', () => {
+ test('the new badge reads ONLY dclv_state, never row.clv/clv_result', () => {
+ const badge = read('web/src/lib/clvBadge.js');
+ expect(badge).toMatch(/dclv_state/);
+ expect(badge).not.toMatch(/clv_result/);
+ expect(badge).not.toMatch(/read\.clv\b/);
+ });
+
+ test('the ledger row renders the directional badge in the old slot', () => {
+ const src = read(SURFACES['ledger card (authed)']);
+ expect(src).toMatch(/ {
+ expect(read(SURFACES['public profile (share link)'])).not.toMatch(/ClvBadge/);
+ });
+});
+
+describe('HONEST ABSENCE — not a subtler "flat"', () => {
+ const { clvBadge } = require('../../web/src/lib/clvBadge');
+
+ test('a formerly-chipped historical row now renders NOTHING', () => {
+ // Exactly the shape of the 578 rows that used to show "CLV 0 · FLAT".
+ const legacyRow = { clv: 0, clv_result: 'flat', locked_odds: '-110', closing_line: 1.5, outcome: 'hit' };
+ expect(clvBadge(legacyRow)).toBeNull();
+ });
+
+ test('rows that will NEVER get dclv (settled pre-capture) stay silent forever', () => {
+ expect(clvBadge({ outcome: 'miss', locked_odds: '-120', dclv_state: null })).toBeNull();
+ });
+
+ test('absence makes NO claim — there is no "flat"/"no movement" output path', () => {
+ const states = ['unknown', 'flat', null, undefined];
+ for (const st of states) {
+ expect(clvBadge({ dclv_state: st, locked_odds: '-110', closing_odds: '-145' })).toBeNull();
+ }
+ });
+});
+
+describe('NO CLV SORT/FILTER (a soft aggregate)', () => {
+ test('the ledger exposes no CLV sort or filter control', () => {
+ const code = stripComments(read(SURFACES['ledger card (authed)']));
+ expect(code).not.toMatch(/sort.*dclv|dclv.*sort/i);
+ expect(code).not.toMatch(/filter.*dclv|dclv.*filter/i);
+ });
+});
diff --git a/web/src/app/dashboard/page.tsx b/web/src/app/dashboard/page.tsx
index d9682da..12e2287 100644
--- a/web/src/app/dashboard/page.tsx
+++ b/web/src/app/dashboard/page.tsx
@@ -505,7 +505,11 @@ export default function DashboardPage() {
✓ HIT{r.actual != null ? ` (${r.actual})` : ''}
- {r.clvResult === 'beat' ? · CLV BEAT : null}
+ {/* Session 64 — legacy C4 CLV removed. It read row.clvResult,
+ derived from the overwritable closing_line (637/699 closing ==
+ locked), so "BEAT" was unreliable and "flat" mostly meant the
+ close was never captured. The verified directional badge is
+ Analyst+Desk on the ledger card; nothing is shown here. */}
))}
diff --git a/web/src/app/ledger/page.tsx b/web/src/app/ledger/page.tsx
index b57b3b9..c818377 100644
--- a/web/src/app/ledger/page.tsx
+++ b/web/src/app/ledger/page.tsx
@@ -8,6 +8,7 @@ import { gradeColor } from '@/lib/vyndrTokens';
import { useAuth } from '@/contexts/AuthContext';
import { Skeleton, EmptyState, ArchetypeBadge, BookWordmark, TierRecord } from '@/components/vyndr';
import { clvMode, CLV_FLAT_LINE } from '@/lib/clvDisplay';
+import ClvBadge from '@/components/vyndr/ClvBadge';
/**
* The Ledger (Session 58, Phase 1) — the truth surface, now backed by the
@@ -316,18 +317,6 @@ function OutcomeChip({ row }: { row: LedgerRow }) {
);
}
-function ClvChip({ row }: { row: LedgerRow }) {
- if (!row.clv_result || row.clv == null) return null;
- const color = row.clv_result === 'beat' ? 'var(--g-a, #00D4A0)'
- : row.clv_result === 'faded' ? 'var(--miss, #FF6B6B)' : 'var(--text-tertiary)';
- return (
-
- CLV {row.clv > 0 ? '+' : ''}{row.clv} · {row.clv_result.toUpperCase()}
-
- );
-}
-
function LedgerCard({ row, index, ladder }: { row: LedgerRow; index: number; ladder?: LedgerRow[] }) {
const sportColor = SPORT_COLOR[row.sport] || 'var(--text-secondary)';
const rungs = Array.isArray(ladder) ? ladder : [row];
@@ -386,7 +375,12 @@ function LedgerCard({ row, index, ladder }: { row: LedgerRow; index: number; lad
-
+ {/* Session 64 — the RESOLVED-AT-CLOSE chapter. Three time states on this
+ row read distinctly: entry (locked_odds, at-grade) · close (this badge,
+ at-close) · outcome (hit/miss, final). The RESULT stays the hero; this
+ is supporting context. Absent dclv renders NOTHING — not a "flat",
+ which is the legacy chip's lie in subtler form. */}
+ } />
);
diff --git a/web/src/app/u/[handle]/PublicProfile.tsx b/web/src/app/u/[handle]/PublicProfile.tsx
index 8c9e3a9..82897fc 100644
--- a/web/src/app/u/[handle]/PublicProfile.tsx
+++ b/web/src/app/u/[handle]/PublicProfile.tsx
@@ -256,17 +256,12 @@ function OutcomeChip({ row }: { row: ProfileRow }) {
);
}
-function ClvChip({ row }: { row: ProfileRow }) {
- if (!row.clv_result || row.clv == null) return null;
- const color = row.clv_result === 'beat' ? 'var(--g-a, #00D4A0)'
- : row.clv_result === 'faded' ? 'var(--miss, #FF6B6B)' : 'var(--text-tertiary)';
- return (
-
- CLV {row.clv > 0 ? '+' : ''}{row.clv} · {row.clv_result.toUpperCase()}
-
- );
-}
+// Session 64 — the legacy C4 CLV chip was REMOVED from this PUBLIC surface.
+// It rendered row.clv/clv_result, built on the overwritable closing_line where
+// 637/699 rows had closing == locked, so 522 of its 578 "flat"s encoded a
+// CAPTURE FAILURE as a held line. Public profiles now show the settled result
+// only. The directional badge is Analyst+Desk and is deliberately NOT added
+// here — this surface is public and never receives dclv data.
function ProfileCard({ row, index }: { row: ProfileRow; index: number }) {
const sportColor = SPORT_COLOR[row.sport] || 'var(--text-secondary)';
@@ -301,7 +296,6 @@ function ProfileCard({ row, index }: { row: ProfileRow; index: number }) {
-
);
diff --git a/web/src/components/Slate.tsx b/web/src/components/Slate.tsx
index 4930107..833fb91 100644
--- a/web/src/components/Slate.tsx
+++ b/web/src/components/Slate.tsx
@@ -419,7 +419,9 @@ function YesterdaySettle({ date }: { date: string }) {
{r.outcome === 'hit' ? '✓ HIT' : r.outcome === 'miss' ? '✕ MISS' : '– PUSH'}{r.actual_value != null ? ` (${r.actual_value})` : ''}
- {r.clv_result && CLV {r.clv_result.toUpperCase()}}
+ {/* Session 64 — legacy C4 CLV removed from the board. Same broken
+ lineage as the ledger chip; the board is also a FREE surface and the
+ verified badge is Analyst+Desk. */}
))}
@@ -512,7 +514,9 @@ function OutlookSurface({ tab }: { tab: SlateTab }) {
{String(r.side).toUpperCase().startsWith('U') ? 'u' : 'o'}{r.line} {String(r.stat).replace(/_/g, ' ')}
- ✓ HIT{r.actual != null ? ` (${r.actual})` : ''}{r.clvResult === 'beat' ? ' · CLV BEAT' : ''}
+ {/* Session 64 — legacy C4 "· CLV BEAT" removed here too; the
+ result stands on its own. */}
+ ✓ HIT{r.actual != null ? ` (${r.actual})` : ''}
))}