// ---------- CPO Thematic view ----------
// Third paper-trading book. TradingAgents-debate-gated thematic basket of
// Co-Packaged Optics / AI-infra names with Aschenbrenner power-layer overlay.

function CpoView() {
  const [data, setData] = useState(null);
  const [serenity, setSerenity] = useState(null);
  const [pulse, setPulse] = useState(null);
  const [error, setError] = useState(null);
  const [expanded, setExpanded] = useState(null);
  const [live, setLive] = useState(null);
  useEffect(() => {
    fetch(`data/cpo_portfolio.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(setData)
      .catch((e) => setError(e.message));
    fetch(`data/serenity_feed.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : null))
      .then(setSerenity)
      .catch(() => setSerenity(null));
    fetch(`data/serenity_pulse.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : null))
      .then(setPulse)
      .catch(() => setPulse(null));
  }, []);
  // Live Alpaca overlay — keeps Serenity NAV fresh between cpo_run fires.
  useEffect(() => {
    let cancelled = false;
    const poll = async () => {
      try {
        const r = await fetch("/api/live?fund=cpo", { cache: "no-store" });
        if (r.ok) { const b = await r.json(); if (!cancelled && b.nav) setLive(b); }
      } catch (_) { /* keep last snapshot */ }
    };
    poll();
    const id = setInterval(poll, 30000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  if (error) return <div style={{ padding: 40, fontFamily: "var(--mono)" }}>Failed to load Serenity Portfolio data: {error}</div>;
  if (!data) return <div style={{ height: "60vh", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)" }}>Loading Serenity Portfolio…</div>;

  const { current = {}, history = [], strategy = {}, positions = [], debate_history = {}, metrics = {} } = data;
  const sorted = [...history].sort((a, b) => a.date.localeCompare(b.date));
  const startNav = sorted[0]?.nav ?? current.nav ?? 100000;
  const currNav = live?.nav ?? current.nav ?? 100000;
  const retSinceStart = startNav > 0 ? (currNav - startNav) / startNav : 0;

  let peak = -Infinity, maxDd = 0;
  for (const h of sorted) {
    peak = Math.max(peak, h.nav);
    const dd = (h.nav - peak) / peak;
    if (dd < maxDd) maxDd = dd;
  }

  // Build PerformanceChart points: {date, value (NAV), benchmark (SPY rebased
  // to the fund's starting NAV)}. spy_inception_close anchors the rebase so
  // the SPY line starts at the same dollar value as the fund.
  const spyInception = data.strategy_meta?.spy_inception_close
    ?? metrics?.spy_inception_close ?? null;
  const cpoPerfPoints = sorted.map((h) => {
    let benchmark = null;
    if (spyInception && h.spy_close) {
      benchmark = startNav * (h.spy_close / spyInception);
    } else if (spyInception && metrics?.counterfactual_spy_nav && h === sorted[sorted.length - 1]) {
      benchmark = metrics.counterfactual_spy_nav;
    }
    return { date: h.date, value: h.nav, benchmark };
  });
  // If we don't have per-snapshot SPY closes, synthesize a 2-point SPY line
  // (inception → now) so the benchmark still renders as a straight reference.
  const hasPerSnapSpy = cpoPerfPoints.some((p) => p.benchmark != null && p !== cpoPerfPoints[cpoPerfPoints.length - 1]);
  if (!hasPerSnapSpy && metrics?.benchmark_spy_return_since_inception != null) {
    const spyRet = metrics.benchmark_spy_return_since_inception;
    cpoPerfPoints.forEach((p, i) => {
      const frac = cpoPerfPoints.length > 1 ? i / (cpoPerfPoints.length - 1) : 1;
      p.benchmark = startNav * (1 + spyRet * frac);
    });
  }

  return (
    <div>
      <section style={{ paddingTop: 60, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 22 }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Signal-driven · conviction-weighted · debate-gated</div>
            <h1 className="h-display" style={{ textWrap: "balance" }}>
              The <em style={{ fontStyle: "italic", color: "var(--accent)" }}>Serenity</em> book.
            </h1>
            <p className="lead" style={{ marginTop: 16 }}>
              A separate Alpaca paper account that fronts-runs the order flow of <strong>@aleabitoreddit</strong> (Serenity) —
              the photonics / AI-supply-chain analyst whose calls (NBIS, AXTI, AAOI, SIVE) have run 3–15× off the bottom.
              The pipeline scrapes her live timeline, parses every <span style={{ fontFamily: "var(--mono)" }}>$TICKER</span>,
              and scores conviction from mention frequency, recency, and tone.
            </p>
            <p className="lead" style={{ marginTop: 12 }}>
              Each name is then run through a <strong>Serenity-tuned TradingAgents debate</strong> — a multi-agent bull/bear
              book that ingests her conviction context alongside price action, AV news sentiment, and StockTwits retail flow.
              Position sizing is a function of <em>both</em> her conviction and the debate verdict:
              <span style={{ fontFamily: "var(--mono)", fontSize: 13.5 }}> OVERWEIGHT/BUY</span> → full size,
              <span style={{ fontFamily: "var(--mono)", fontSize: 13.5 }}> high-conviction starter</span> → 5% NAV tranche,
              <span style={{ fontFamily: "var(--mono)", fontSize: 13.5 }}> thesis-tail</span> → tiny 0.5% lotto on names the
              tape hasn't confirmed yet. We <em>risk-filter</em> her, not replicate her: chase the structurally durable,
              fade the blow-up-prone.
            </p>
            <p className="lead" style={{ marginTop: 12, color: "var(--ink-3)", fontSize: 14.5 }}>
              Exits are symmetric — hard −12% stop, 50% trim into binary earnings prints, 25% trim when she turns bearish on
              a held name, and a daily re-debate sweep that cuts positions the agents flip to SELL. Non-US names she pushes
              (SIVE, LPK, SOI, IQE) get debated for the analyst read but aren't tradeable on Alpaca. Fully autonomous:
              scrape → debate → size → fill, fired on a schedule and on every laptop wake.
            </p>
          </div>

          {/* Allocation donut with time slider */}
          <div style={{
            border: "1px solid var(--line)",
            borderRadius: 12,
            background: "var(--bg-card)",
            padding: "24px 28px",
            marginTop: 24,
          }}>
            {sorted.length >= 2 ? (
              <TimeSliderDonut series={withLivePoint(buildCpoAllocationSeries(sorted, positions), live)} />
            ) : (
              <AllocationDonut
                slices={buildCpoCurrentSlices(current, positions)}
                total={currNav}
                subTotal="hover for breakdown"
              />
            )}
          </div>

          <div style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
            border: "1px solid var(--line)",
            borderRadius: 12,
            overflow: "hidden",
            background: "var(--bg-card)",
            marginTop: 24,
          }}>
            <HeroStat label="Net asset value" value={fmtUSD(currNav, { decimals: 0 })} sub={`from $${startNav.toFixed(0)} seed`} />
            <HeroStat
              label="Return since inception"
              value={<span className={retSinceStart >= 0 ? "pos" : "neg"}>{retSinceStart >= 0 ? "+" : ""}{(retSinceStart * 100).toFixed(2)}%</span>}
              sub={metrics?.benchmark_spy_return_since_inception != null ? `S&P 500: ${(metrics.benchmark_spy_return_since_inception * 100).toFixed(2)}%` : `over ${sorted.length} snapshots`}
            />
            {metrics?.alpha_vs_spy_pp != null && (
              <HeroStat
                label="Alpha vs S&P 500"
                value={<span className={metrics.alpha_vs_spy_pp >= 0 ? "pos" : "neg"}>{metrics.alpha_vs_spy_pp >= 0 ? "+" : ""}{metrics.alpha_vs_spy_pp.toFixed(2)}pp</span>}
                sub={`vs 100% SPY ${fmtUSD(metrics.counterfactual_spy_nav || 0, { decimals: 0 })}`}
              />
            )}
            <HeroStat label="Max drawdown" value={<span className={maxDd >= 0 ? "pos" : "neg"}>{(maxDd * 100).toFixed(2)}%</span>} sub="peak-to-trough" />
            <HeroStat label="Deployed" value={`${((current.deployed_pct || 0) * 100).toFixed(1)}%`} sub={`${positions.length} of ${(strategy?.universe || []).length} confirmed`} />
          </div>
        </div>
      </section>

      {/* Performance chart — NAV vs SPY, same component as the LLM fund */}
      {cpoPerfPoints.length >= 2 && (
        <section style={{ paddingTop: 10, paddingBottom: 20 }}>
          <div className="container">
            <div style={{ marginBottom: 14 }}>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Performance</div>
              <h2 className="h-section">NAV vs S&amp;P 500</h2>
            </div>
            <div className="card" style={{ padding: "28px 32px" }}>
              <PerformanceChart points={cpoPerfPoints} showBenchmark={true} fund="cpo" liveValue={live?.nav ?? null} />
            </div>
          </div>
        </section>
      )}

      {/* Positions table */}
      <section style={{ paddingTop: 20, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 18 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Holdings</div>
            <h2 className="h-section">Confirmed positions</h2>
          </div>
          <div className="card" style={{ padding: "20px 28px" }}>
            {positions.length === 0 ? (
              <p className="muted" style={{ padding: 20, textAlign: "center" }}>
                No positions yet — initial TradingAgents debate may still be running or hasn't been triggered yet.
              </p>
            ) : (
              <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
                <thead>
                  <tr style={{ borderBottom: "1px solid var(--line)" }}>
                    <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Qty</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Avg cost</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Last</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>MV</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>PnL</th>
                  </tr>
                </thead>
                <tbody>
                  {positions
                    .slice()
                    .sort((a, b) => (b.market_value || 0) - (a.market_value || 0))
                    .map((p) => (
                      <tr key={p.ticker} style={{ borderBottom: "1px solid var(--line)" }}>
                        <td style={{ padding: "12px 0", fontWeight: 500 }}>{p.ticker}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>{(p.qty || 0).toFixed(4)}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>${(p.avg_cost || 0).toFixed(2)}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>${(p.last_price || 0).toFixed(2)}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>{fmtUSD(p.market_value || 0, { decimals: 0 })}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }} className={(p.unrealized_pnl_pct || 0) >= 0 ? "pos" : "neg"}>
                          {(p.unrealized_pnl_pct || 0) >= 0 ? "+" : ""}{((p.unrealized_pnl_pct || 0) * 100).toFixed(2)}%
                        </td>
                      </tr>
                    ))}
                </tbody>
              </table>
            )}
          </div>
        </div>
      </section>

      {/* TradingAgents debate transcripts */}
      <section style={{ paddingTop: 20, paddingBottom: 60 }}>
        <div className="container">
          <div style={{ marginBottom: 18 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Debate transcripts</div>
            <h2 className="h-section">TradingAgents per-name decisions</h2>
          </div>
          <div className="card" style={{ padding: "8px 24px 16px" }}>
            {Object.keys(debate_history).length === 0 ? (
              <p className="muted" style={{ padding: 20, textAlign: "center" }}>
                Debate hasn't run yet. Will populate after first daemon fire.
              </p>
            ) : (
              <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
                <thead>
                  <tr style={{ borderBottom: "1px solid var(--line)" }}>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Signal</th>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Date</th>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Rationale (click to expand)</th>
                  </tr>
                </thead>
                <tbody>
                  {Object.entries(debate_history)
                    .sort((a, b) => (a[1].date || "").localeCompare(b[1].date || ""))
                    .reverse()
                    .map(([ticker, rec]) => {
                      const isOpen = expanded === ticker;
                      const sig = (rec.signal || "—").toUpperCase();
                      const isBuy = sig === "BUY" || sig === "OVERWEIGHT";
                      const isSell = sig === "SELL" || sig === "UNDERWEIGHT";
                      return (
                        <tr key={ticker} onClick={() => setExpanded(isOpen ? null : ticker)}
                            style={{ borderBottom: "1px solid var(--line)", cursor: "pointer", verticalAlign: "top" }}>
                          <td style={{ padding: "10px 0", fontWeight: 500 }}>{ticker}</td>
                          <td style={{ padding: "10px 0" }} className={isBuy ? "pos" : isSell ? "neg" : ""}>{sig}</td>
                          <td style={{ padding: "10px 0", color: "var(--ink-3)", fontFamily: "var(--mono)", fontSize: 12 }}>{(rec.date || "").slice(0, 10)}</td>
                          <td style={{ padding: "10px 0", fontSize: 13, color: "var(--ink-2)", maxWidth: 540, whiteSpace: isOpen ? "pre-wrap" : "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                            {rec.error ? <em style={{ color: "var(--neg, #c0392b)" }}>error: {rec.error}</em> : (rec.decision_excerpt || "—")}
                          </td>
                        </tr>
                      );
                    })}
                </tbody>
              </table>
            )}
          </div>
          <p className="muted" style={{ fontSize: 12.5, marginTop: 14 }}>
            Click any row to expand the full debate excerpt. Re-debate fires every Monday on all 11 candidates.
          </p>
        </div>
      </section>

      {/* Serenity Pulse — top mentions + 30d heatmap + tweet stream */}
      <SerenityPulse pulse={pulse} />

      {/* Serenity feed (today's actions table) */}
      <SerenityFeed serenity={serenity} />

      <section style={{ paddingTop: 0, paddingBottom: 80 }}>
        <div className="container">
          <div className="card" style={{ padding: "24px 32px", background: "var(--bg-card)" }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Aschenbrenner overlay</div>
            <p style={{ fontSize: 15, lineHeight: 1.6 }}>
              The universe extends beyond pure-CPO names to include Leopold Aschenbrenner's Situational Awareness LP top-5
              holdings — <strong>BE</strong> (Bloom Energy, his #2), <strong>CORZ</strong> (Core Scientific),
              <strong> CRWV</strong> (CoreWeave) — under the thesis that AI scale is power-constrained, not chip-constrained.
              <strong> COHR</strong> and <strong>LITE</strong> are also in his fund. The TradingAgents debate ultimately decides
              which names earn an allocation; the universe is wider than pure CPO to let bull/bear arguments span the broader
              AI-infrastructure thesis.
            </p>
          </div>
        </div>
      </section>
    </div>
  );
}

// ---------- Serenity Pulse ----------
// 3-layer view of Serenity's tweet activity:
//   1. Top mentions last 14d (bars + our latest debate verdict)
//   2. 30-day heatmap (rows=tickers, cols=days, cell color=mention count,
//      overlay badge when we debated that ticker that day)
//   3. Tweet stream with inline ticker chips colored by debate verdict
//
// Click a ticker chip anywhere → filters all three rows to that ticker.
// Data source: web/data/serenity_pulse.json (built by scripts/build_serenity_pulse.py).
function SerenityPulse({ pulse }) {
  const [selected, setSelected] = useState(null); // ticker filter (null = all)
  if (!pulse) return null;
  const { top_tickers = [], heatmap = [], stream = [], days = [], stats = {} } = pulse;
  const filtered_stream = selected
    ? stream.filter((t) => t.tickers.some((tk) => tk.ticker === selected))
    : stream;

  // Color helpers
  const signalColor = (sig) => {
    const s = (sig || "").toUpperCase();
    if (s === "BUY" || s === "OVERWEIGHT") return "var(--pos, #2da465)";
    if (s === "SELL" || s === "UNDERWEIGHT") return "var(--neg, #d04848)";
    if (s === "HOLD") return "var(--ink-3, #888)";
    return "var(--line, #444)";
  };
  const signalAbbrev = (sig) => {
    const s = (sig || "").toUpperCase();
    if (s === "OVERWEIGHT") return "OW";
    if (s === "UNDERWEIGHT") return "UW";
    return s || "";
  };
  // Heatmap cell intensity: 0 → background, 1+ → progressively bolder
  const cellColor = (count) => {
    if (!count) return "rgba(120,140,170,0.06)";
    if (count === 1) return "rgba(61,232,160,0.22)";
    if (count === 2) return "rgba(61,232,160,0.42)";
    if (count === 3) return "rgba(61,232,160,0.62)";
    if (count === 4) return "rgba(61,232,160,0.82)";
    return "rgba(61,232,160,1)";
  };

  // Pill rendering for ticker chip
  const Chip = ({ ticker, signal, action, non_us, onClick, dim }) => {
    const isSelected = selected === ticker;
    const baseColor = non_us ? "var(--ink-3)" : signalColor(signal);
    return (
      <span
        onClick={onClick}
        title={
          non_us
            ? `${ticker} — non-US (not Alpaca-tradeable)`
            : signal
            ? `${ticker} — last debate: ${signal}${action ? ` (${action})` : ""}`
            : `${ticker} — no debate yet`
        }
        style={{
          display: "inline-flex",
          alignItems: "center",
          gap: 4,
          padding: "2px 8px",
          borderRadius: 999,
          fontSize: 11.5,
          fontFamily: "var(--mono)",
          fontWeight: 500,
          border: `1px solid ${baseColor}`,
          color: dim ? "var(--ink-3)" : baseColor,
          background: isSelected ? `${baseColor}22` : "transparent",
          cursor: onClick ? "pointer" : "default",
          opacity: non_us ? 0.55 : 1,
          marginRight: 5,
          marginBottom: 4,
          transition: "background 120ms",
        }}
      >
        ${ticker}
        {signal && !non_us && (
          <span style={{ fontSize: 9.5, opacity: 0.85 }}>{signalAbbrev(signal)}</span>
        )}
      </span>
    );
  };

  return (
    <section style={{ paddingTop: 0, paddingBottom: 50 }}>
      <div className="container">
        <div style={{ marginBottom: 18 }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>Serenity Pulse</div>
          <h2 className="h-section">What @aleabitoreddit is pushing</h2>
          <p className="lead" style={{ marginTop: 8 }}>
            Top mentions (14d window), a {pulse.window_days_heatmap}-day mention heatmap, and the raw tweet
            stream — each ticker chip colored by our most recent TradingAgents debate verdict.
            Click any ticker to filter the stream.
            <span style={{ color: "var(--ink-3)", marginLeft: 12, fontFamily: "var(--mono)", fontSize: 12 }}>
              {stats.tweets_total} tweets · {stats.debates_total} debates · {stats.tickers_tracked} tickers tracked
            </span>
          </p>
          {selected && (
            <div style={{ marginTop: 12 }}>
              <span style={{ fontSize: 13, marginRight: 10 }}>Filtering by:</span>
              <Chip ticker={selected} signal={top_tickers.find((t) => t.ticker === selected)?.latest_signal}
                    non_us={top_tickers.find((t) => t.ticker === selected)?.non_us}
                    onClick={() => setSelected(null)} />
              <button onClick={() => setSelected(null)}
                style={{ background: "transparent", border: "none", color: "var(--ink-3)", cursor: "pointer", fontSize: 12, textDecoration: "underline" }}>
                clear filter
              </button>
            </div>
          )}
        </div>

        {/* === Row 1: Top mentions bar list === */}
        {top_tickers.length > 0 && (
          <div className="card" style={{ padding: "20px 28px", marginBottom: 18 }}>
            <h3 style={{ marginTop: 0, marginBottom: 14, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              Most mentioned (last {pulse.window_days_top_mentions} days)
            </h3>
            <div>
              {top_tickers.map((t) => {
                const maxCount = Math.max(...top_tickers.map((x) => x.mentions_14d));
                const pct = (t.mentions_14d / maxCount) * 100;
                const color = t.non_us ? "var(--ink-3)" : signalColor(t.latest_signal);
                return (
                  <div key={t.ticker}
                       onClick={() => setSelected(selected === t.ticker ? null : t.ticker)}
                       style={{
                         display: "grid",
                         gridTemplateColumns: "70px 1fr 90px 100px",
                         alignItems: "center",
                         gap: 12,
                         padding: "6px 0",
                         cursor: "pointer",
                         opacity: t.non_us ? 0.7 : 1,
                         background: selected === t.ticker ? "rgba(61,232,160,0.06)" : "transparent",
                         borderRadius: 4,
                       }}>
                    <div style={{ fontFamily: "var(--mono)", fontWeight: 600, fontSize: 13 }}>
                      ${t.ticker}
                    </div>
                    <div style={{ position: "relative", height: 18, background: "rgba(120,140,170,0.08)", borderRadius: 3 }}>
                      <div style={{
                        position: "absolute", left: 0, top: 0, bottom: 0, width: `${pct}%`,
                        background: color, opacity: 0.55, borderRadius: 3,
                      }} />
                    </div>
                    <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink-3)" }}>
                      {t.mentions_14d}× mentions
                    </div>
                    <div style={{ fontFamily: "var(--mono)", fontSize: 11, color, fontWeight: 500 }}>
                      {t.non_us ? "non-US" : t.latest_signal ? `→ ${t.latest_signal}` : "—"}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        {/* === Row 2: 30-day heatmap === */}
        {heatmap.length > 0 && (
          <div className="card" style={{ padding: "20px 28px", marginBottom: 18, overflowX: "auto" }}>
            <h3 style={{ marginTop: 0, marginBottom: 14, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              {pulse.window_days_heatmap}-day activity heatmap
            </h3>
            <table style={{ borderCollapse: "collapse", fontFamily: "var(--mono)", fontSize: 10 }}>
              <thead>
                <tr>
                  <th style={{ position: "sticky", left: 0, background: "var(--bg-card, #1a1a1f)", padding: "0 8px 8px 0", textAlign: "left", color: "var(--ink-3)", fontWeight: 400, minWidth: 60 }}>Ticker</th>
                  {days.map((d) => {
                    // Show every 5th day label
                    const dayNum = parseInt(d.slice(-2), 10);
                    const showLabel = dayNum % 5 === 0 || d === days[days.length - 1];
                    return (
                      <th key={d} style={{ padding: "0 1px 8px", color: "var(--ink-3)", fontWeight: 400, fontSize: 9, minWidth: 16, textAlign: "center" }}>
                        {showLabel ? d.slice(5) : ""}
                      </th>
                    );
                  })}
                </tr>
              </thead>
              <tbody>
                {heatmap.map((row) => {
                  const isSelected = selected === row.ticker;
                  // Highlight ticker label cell green if the latest debate
                  // verdict is BUY/OVERWEIGHT. Non-buys stay neutral so the
                  // eye is drawn to actionable names.
                  const isBuy = !row.non_us && row.latest_signal &&
                                ["BUY", "OVERWEIGHT"].includes(row.latest_signal.toUpperCase());
                  const labelBg = isBuy
                    ? "rgba(61,232,160,0.22)"
                    : "var(--bg-card, #1a1a1f)";
                  return (
                    <tr key={row.ticker} style={{ opacity: selected && selected !== row.ticker ? 0.35 : 1, transition: "opacity 120ms" }}>
                      <td
                        onClick={() => setSelected(isSelected ? null : row.ticker)}
                        style={{
                          position: "sticky", left: 0, background: labelBg,
                          padding: "4px 8px 4px 8px", fontWeight: 600, fontSize: 11,
                          cursor: "pointer",
                          color: row.non_us ? "var(--ink-3)" : (isBuy ? "var(--pos, #2da465)" : "inherit"),
                          textDecoration: isSelected ? "underline" : "none",
                          borderLeft: isBuy ? "3px solid var(--pos, #2da465)" : "3px solid transparent",
                        }}>
                        ${row.ticker}
                      </td>
                      {row.cells.map((cell) => (
                        <td key={cell.day}
                            title={cell.count > 0
                              ? `${row.ticker} on ${cell.day}: ${cell.count} mention${cell.count > 1 ? "s" : ""}${cell.debate_signal ? ` · we debated → ${cell.debate_signal}` : ""}`
                              : ""}
                            style={{
                              padding: 0, minWidth: 16, height: 16,
                              background: cellColor(cell.count),
                              border: "1px solid rgba(0,0,0,0.15)",
                              position: "relative",
                            }}>
                          {cell.debate_signal && (
                            <span style={{
                              position: "absolute", inset: 0, display: "flex",
                              alignItems: "center", justifyContent: "center",
                              color: signalColor(cell.debate_signal), fontSize: 10, fontWeight: 700,
                            }}>•</span>
                          )}
                        </td>
                      ))}
                    </tr>
                  );
                })}
              </tbody>
            </table>
            <p style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 12, fontFamily: "var(--mono)" }}>
              Cell intensity = mention count that day. Dot inside cell = we ran a TradingAgents debate
              on that day (color = signal: <span style={{ color: signalColor("BUY") }}>● BUY/OW</span> ·
              <span style={{ color: signalColor("HOLD") }}> ● HOLD</span> ·
              <span style={{ color: signalColor("SELL") }}> ● SELL/UW</span>).
            </p>
          </div>
        )}

        {/* === Row 3: Tweet stream === */}
        {filtered_stream.length > 0 && (
          <div className="card" style={{ padding: "20px 28px" }}>
            <h3 style={{ marginTop: 0, marginBottom: 14, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              {selected ? `Tweets mentioning $${selected}` : "Recent tweet stream"}
              <span style={{ marginLeft: 10, color: "var(--ink-3)", fontWeight: 400 }}>
                ({filtered_stream.length} tweet{filtered_stream.length !== 1 ? "s" : ""})
              </span>
            </h3>
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {filtered_stream.map((t) => {
                const dt = new Date(t.posted_at);
                const hoursAgo = (Date.now() - dt.getTime()) / 3.6e6;
                const ageLabel = hoursAgo < 1
                  ? `${Math.round(hoursAgo * 60)}m ago`
                  : hoursAgo < 24
                  ? `${Math.round(hoursAgo)}h ago`
                  : `${Math.round(hoursAgo / 24)}d ago`;
                return (
                  <div key={t.id} style={{
                    padding: "12px 16px",
                    borderLeft: "2px solid var(--line, #333)",
                    background: "rgba(120,140,170,0.04)",
                    borderRadius: "0 4px 4px 0",
                  }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6, fontSize: 11.5, color: "var(--ink-3)", fontFamily: "var(--mono)" }}>
                      <span style={{ fontWeight: 500 }}>@{t.handle}</span>
                      <span>·</span>
                      <span>{ageLabel}</span>
                      <span style={{ marginLeft: "auto", fontSize: 10 }}>{dt.toISOString().slice(0, 16).replace("T", " ")}</span>
                    </div>
                    <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.55, whiteSpace: "pre-wrap" }}>
                      {t.text}
                    </p>
                    {t.tickers.length > 0 && (
                      <div style={{ marginTop: 8 }}>
                        {t.tickers.map((tk, i) => (
                          <Chip key={`${tk.ticker}-${i}`}
                                ticker={tk.ticker}
                                signal={tk.signal}
                                action={tk.action}
                                non_us={tk.non_us}
                                onClick={() => setSelected(selected === tk.ticker ? null : tk.ticker)} />
                        ))}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </div>
    </section>
  );
}

// ---------- Serenity / external-signal feed ----------
// Renders the daily Serenity (@aleabitoreddit) + endorsed-handle scrape:
// recent tweets with ticker tags, debate outcomes per ticker, and the
// system's action (buy / skip / deferred).
function SerenityFeed({ serenity }) {
  if (!serenity) return null;
  const run = serenity.latest_run || {};
  const tweets = serenity.recent_tweets || [];
  const actions = run.actions || [];

  return (
    <section style={{ paddingTop: 0, paddingBottom: 40 }}>
      <div className="container">
        <div style={{ marginBottom: 18 }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>External signal feed</div>
          <h2 className="h-section">Serenity + endorsed analysts</h2>
          <p className="lead" style={{ marginTop: 8 }}>
            Daily scrape of @aleabitoreddit (Serenity — the original WSB / AI-Semi supply chain analyst) plus accounts
            she's endorsed. Tickers mentioned across the feed get filtered to US-tradeable names, deduped against current
            holdings, and the top {2} new candidates run through a TradingAgents debate.
            Only BUY/OVERWEIGHT confirmed names get bought at 10% NAV. The rest is research, not signal.
          </p>
        </div>

        {/* Actions this run */}
        {actions.length > 0 && (
          <div className="card" style={{ padding: "20px 28px", marginBottom: 18 }}>
            <h3 style={{ marginTop: 0, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              Today's debate outcomes
            </h3>
            <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
              <thead>
                <tr style={{ borderBottom: "1px solid var(--line)" }}>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Mentions</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>TA signal</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Action</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Notional</th>
                </tr>
              </thead>
              <tbody>
                {actions.map((a) => {
                  const sig = (a.signal || "").toUpperCase();
                  const isBuy = sig === "BUY" || sig === "OVERWEIGHT";
                  const isSell = sig === "SELL" || sig === "UNDERWEIGHT";
                  return (
                    <tr key={a.ticker} style={{ borderBottom: "1px solid var(--line)" }}>
                      <td style={{ padding: "10px 0", fontWeight: 500 }}>{a.ticker}</td>
                      <td style={{ padding: "10px 0", color: "var(--ink-3)" }}>{a.mention_count}×</td>
                      <td style={{ padding: "10px 0" }} className={isBuy ? "pos" : isSell ? "neg" : ""}>{sig || "—"}</td>
                      <td style={{ padding: "10px 0" }}>{a.action || "—"}</td>
                      <td style={{ padding: "10px 0", color: "var(--ink-3)" }}>{a.notional ? `$${a.notional.toFixed(0)}` : "—"}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            {run.candidates_deferred?.length > 0 && (
              <p className="muted" style={{ marginTop: 12, fontSize: 12.5 }}>
                Deferred to tomorrow (over daily cap): <strong>{run.candidates_deferred.join(", ")}</strong>
              </p>
            )}
          </div>
        )}

        {/* Recent tweets */}
        <div className="card" style={{ padding: "20px 28px" }}>
          <h3 style={{ marginTop: 0, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
            Recent tweets ({tweets.length})
          </h3>
          {tweets.length === 0 ? (
            <p className="muted" style={{ padding: 16, textAlign: "center" }}>
              No tweets pulled yet — first daemon run hasn't completed.
            </p>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {tweets.slice(0, 12).map((t, i) => (
                <div key={i} style={{ paddingBottom: 14, borderBottom: "1px solid var(--line)" }}>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 }}>
                    <span style={{ fontWeight: 500, fontSize: 13 }}>@{t.handle}</span>
                    <span className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{t.date}</span>
                    {t.tickers?.map((sym) => (
                      <span key={sym} className="mono" style={{
                        fontSize: 11, padding: "2px 6px", borderRadius: 3,
                        background: "var(--bg-card)", border: "1px solid var(--line)",
                        color: "var(--accent)",
                      }}>${sym}</span>
                    ))}
                  </div>
                  <div style={{ fontSize: 13.5, lineHeight: 1.5, color: "var(--ink-2)" }}>
                    {t.text}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* New shoutouts to consider adding to seed list */}
        {run.new_mentioned_handles?.length > 0 && (
          <p className="muted" style={{ fontSize: 12.5, marginTop: 14 }}>
            New @-mentions surfaced in today's feed (potential additions to watch list — review manually):{" "}
            {run.new_mentioned_handles.slice(0, 15).map((h) => (
              <span key={h} className="mono" style={{ marginRight: 8 }}>@{h}</span>
            ))}
          </p>
        )}
      </div>
    </section>
  );
}
