Session F (night2): Phase 5 — records + dossier

5.1 Archetype defined on-page: one line from the archetype library under
    ARCHETYPE DNA (BOMBER — elite raw power…); expander keeps the long form.
5.2 VYNDR-on-team live: getModelAggregate team scope (migration-020 column)
    + /api/ledger/model?team= + ModelRecord mounted on the Team Hub header.
5.3 WNBA/NBA profile parity: minutes-based usage (+MIN season cell) when
    the feed carries minutes — absent beats invented.
5.4 Settings read meter: real rolling-24h usage from the SAME store the
    limiter enforces (GET /api/user/scan-meter + proxy). Metered tiers see
    'X of N reads today' + bar; unlimited tiers see nothing. Corrects the
    stale '5 scans / month' copy.
5.5 Per-tier calibration on the MODEL tab: A+/A/B/C chips with hit% at
    n>=20 PER TIER, 'building (n/20)' below — the separation between tiers
    is the proof the grades mean something.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-11 01:05:29 -04:00
parent 5d19660f8e
commit f110bd63f1
13 changed files with 206 additions and 4 deletions
+34 -1
View File
@@ -68,6 +68,18 @@ export default function SettingsPage() {
const [deleteText, setDeleteText] = useState('');
const [deleting, setDeleting] = useState(false);
const [delError, setDelError] = useState('');
// Session 60 (5.4) — the daily read meter (metered tiers only).
const [meter, setMeter] = useState<{ unlimited: boolean; used: number | null; limit: number | null; remaining?: number | null } | null>(null);
useEffect(() => {
const token = session?.access_token;
if (!token) return;
let active = true;
fetch('/api/user/scan-meter', { headers: { Authorization: `Bearer ${token}` } })
.then((r) => (r.ok ? r.json() : null))
.then((data) => { if (active && data && typeof data.unlimited === 'boolean') setMeter(data); })
.catch(() => { /* meter hides itself */ });
return () => { active = false; };
}, [session]);
// Session 49 — onboarding preferences (load + edit + save).
const [prefSports, setPrefSports] = useState<string[]>([]);
@@ -162,10 +174,31 @@ export default function SettingsPage() {
{/* SUBSCRIPTION */}
<Section label="SUBSCRIPTION">
{/* Session 60 (5.4) — the daily read meter for METERED tiers, from
the live limiter's rolling-24h window (never disagrees with the
gate). Unlimited tiers hide it. */}
{meter && !meter.unlimited && meter.limit != null && (
<>
<Row>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, color: 'var(--text-0)' }}>Reads today</div>
<div style={{ marginTop: 8, height: 6, background: 'var(--bg-3)', borderRadius: 3, overflow: 'hidden', maxWidth: 260 }}>
<div style={{ width: `${Math.min(100, Math.round(((meter.used ?? 0) / meter.limit) * 100))}%`, height: '100%', background: (meter.remaining ?? 1) <= 0 ? 'var(--miss)' : 'var(--g-a)', borderRadius: 3 }} />
</div>
</div>
<span className="mono" style={{ fontSize: 13, fontWeight: 700, color: (meter.remaining ?? 1) <= 0 ? 'var(--miss)' : 'var(--text-0)' }}>
{meter.used ?? 0} of {meter.limit}
</span>
</Row>
<Divider />
</>
)}
<Row>
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: '#fff' }}>VYNDR {plan.label === 'FREE' ? 'Free' : plan.label === 'DESK' ? 'Desk' : 'Analyst'}</div>
<div className="mono" style={{ fontSize: 12, color: 'var(--text-1)', marginTop: 3 }}>{plan.label === 'FREE' ? '5 scans / month' : 'Active subscription'}</div>
<div className="mono" style={{ fontSize: 12, color: 'var(--text-1)', marginTop: 3 }}>
{meter && !meter.unlimited && meter.limit != null ? `${meter.limit} reads / day` : plan.label === 'FREE' ? '3 reads / day' : 'Active subscription'}
</div>
</div>
<div style={{ display: 'flex', gap: 9 }}>
{plan.label !== 'FREE' && (