/* ============================================================================ StonksScout — APP SHELL (nav, routing, data load) Production adaptation of the Claude Design prototype: - The design-time Tweaks panel (tweaks-panel.jsx) is removed; the chosen identity — Light mode + Tennessee Orange (#ff8200) — is baked into index.html ( + --accent). - Data loads from ./stonks_export.json (the StonksScout pipeline output) when present, and falls back to the bundled sample data in data.js otherwise. Field names match the design spec exactly. ============================================================================ */ const { useState, useEffect } = React; const NAV = [ { id:"today", label:"Today's Verdicts", short:"Verdicts" }, { id:"detail", label:"Ticker Detail", short:"Detail" }, { id:"watchlist", label:"Watchlist", short:"Watchlist" }, { id:"ledger", label:"Performance Ledger", short:"Ledger" }, ]; const byConviction = (a,b)=> b.conviction - a.conviction; function Logo(){ return (
StonksScout
DAILY EQUITY VERDICTS
); } function MarketChip({ open, lastRun }){ const c = open ? "#17a86a" : "#8a929e"; return (
{open && } {open ? "Market Open" : "Market Closed"} {lastRun}
); } function App(){ const [tab, setTab] = useState("today"); const [data, setData] = useState({ verdicts: MOCK_VERDICTS, watchlist: MOCK_WATCHLIST, ledger: MOCK_LEDGER, meta: null, }); const [ticker, setTicker] = useState([...MOCK_VERDICTS].sort(byConviction)[0].ticker); // Load live pipeline output if present; otherwise keep the bundled sample data. useEffect(()=>{ let cancelled = false; fetch("stonks_export.json", { cache:"no-store" }) .then(r => r.ok ? r.json() : null) .then(j => { if(cancelled || !j || !Array.isArray(j.verdicts) || !j.verdicts.length) return; setData({ verdicts: j.verdicts, watchlist: Array.isArray(j.watchlist) ? j.watchlist : MOCK_WATCHLIST, ledger: Array.isArray(j.ledger) ? j.ledger : MOCK_LEDGER, meta: j.meta || {}, }); setTicker([...j.verdicts].sort(byConviction)[0].ticker); }) .catch(()=>{ /* offline / no export yet → sample data stays */ }); return ()=>{ cancelled = true; }; },[]); const verdictByTicker = (tk)=> data.verdicts.find(v=>v.ticker===tk) || data.verdicts[0]; const openTicker = (tk)=>{ if(data.verdicts.some(v=>v.ticker===tk)){ setTicker(tk); setTab("detail"); window.scrollTo({top:0}); } }; // detail prev/next across conviction-sorted verdicts const sortedVerdicts = [...data.verdicts].sort(byConviction); const curIdx = Math.max(0, sortedVerdicts.findIndex(v=>v.ticker===ticker)); const stepTicker = (d)=>{ const n = (curIdx + d + sortedVerdicts.length) % sortedVerdicts.length; setTicker(sortedVerdicts[n].ticker); window.scrollTo({top:0}); }; const isLive = !!data.meta; const lastRun = (data.meta && data.meta.last_run_et) || "06:40 ET"; const marketOpen= (data.meta && typeof data.meta.market_open === "boolean") ? data.meta.market_open : true; const buildStr = (data.meta && data.meta.build) || "2026.05.30"; return (
{/* top bar */}
JS
{/* mobile nav */}
{tab==="today" && } {tab==="detail" && setTab("today")} onPrev={()=>stepTicker(-1)} onNext={()=>stepTicker(1)} />} {tab==="watchlist" &&} {tab==="ledger" && }
); } ReactDOM.createRoot(document.getElementById("root")).render();