import { useState, useEffect, useCallback } from "react"; import { TIERS } from "../lib/tradeGate.js"; import { getTierRank, isTierClickable } from "../lib/tierNavigation.js"; import AutomatedSetupModal from "./AutomatedSetupModal.jsx"; /** * Dashboard.jsx * * Design intent: this is a trading terminal, not a marketing page -- * dense, precise, calm. The tier ladder (dashboard -> manual -> * automated) is rendered as a real structural element because it IS * the real security model (see tradeGate.js), not decoration. Numbers * that came from the walk-forward-validated baseline are visually * distinguished from numbers the live adaptive layer just computed -- * conflating "this was proven out-of-sample" with "this is a live * guess" in the UI would be the same category of silent-failure this * whole project has consistently flagged as the dangerous thing to do. */ const TIER_INFO = [ { key: TIERS.DASHBOARD, label: "Dashboard", desc: "Read-only. Balances and strategy status." }, { key: TIERS.MANUAL, label: "Manual approval", desc: "See signals, tap to confirm, sign on Ledger." }, { key: TIERS.AUTOMATED, label: "Automated", desc: "Runs within limits you set. Ledger confirmation still required per trade." }, ]; function TierLadder({ currentTier, onRequestTierChange }) { const currentRank = getTierRank(currentTier); return (
{TIER_INFO.map((tier, i) => { const active = i === currentRank; const clickable = isTierClickable(currentTier, tier.key); const reachable = clickable || active; return ( ); })}
); } function NumberRow({ label, value, source, mono = true }) { const sourceColor = source === "walk_forward_validated" ? "#5fb87a" : source === "conservative_default" ? "#8a8d93" : "#c9a24b"; return (
{label} {value} {source && }
); } export default function Dashboard({ gate, ledgerSupport }) { const [snapshot, setSnapshot] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [tier, setTier] = useState(gate.tier); const [automatedModalOpen, setAutomatedModalOpen] = useState(false); const refresh = useCallback(async () => { try { setLoading(true); const s = await gate.getDashboardSnapshot(); setSnapshot(s); setError(null); } catch (e) { setError(e.message); } finally { setLoading(false); } }, [gate]); useEffect(() => { refresh(); }, [refresh]); const handleTierChange = (key) => { if (key === TIERS.DASHBOARD) { // FIXED -- this button rendered clickable at manual/automated tiers // (see TierLadder's `reachable` check) but had no matching branch // here, so tapping it silently did nothing. Confirmed empirically // before this fix, not assumed. De-escalating trust should never // be the hard path, and "hard path" turns out to have included // "doesn't work at all." gate.disableTrading(); setTier(gate.tier); } else if (key === TIERS.MANUAL) { gate.enableManual(); setTier(gate.tier); } else if (key === TIERS.AUTOMATED) { setAutomatedModalOpen(true); } }; const handleConfirmAutomated = async ({ maxPerTradeUsd, maxDailyTradesCount }) => { // Let a thrown TradeGateError propagate -- AutomatedSetupModal catches // it and displays it inline. Only close the modal and advance tier // state on genuine success, so a rejected attempt never looks like it // silently succeeded. gate.enableAutomated({ maxPerTradeUsd, maxDailyTradesCount }); setTier(gate.tier); setAutomatedModalOpen(false); }; const usdBalance = snapshot?.balances?.find((b) => b.asset === "USD")?.available ?? null; return (
ASSET
{gate.asset.toUpperCase()}
{!ledgerSupport.supported && (
Ledger unavailable in this browser: {ledgerSupport.reason}
)} {error && (
{error}
)}
{snapshot?.balances?.map((b) => ( ))}
CAPABILITY
\u25CF walk-forward validated \u25CF conservative default
setAutomatedModalOpen(false)} onConfirm={handleConfirmAutomated} usdBalance={usdBalance} />
); }