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:
@@ -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' && (
|
||||
|
||||
Reference in New Issue
Block a user