/* briefs.jsx, the notifications hub behind the top-right bell.
   Read-focused by design: market recaps, price alerts, confirmations, and
   failure alerts. Retry CTAs hand off to Yoshi chat; approvals live in
   Stream → Needs you. */

/* ---- archived-briefs store · "Mark as read" and swipe-to-archive both move a
   brief here. Persisted to localStorage; broadcasts so the Stream briefs board
   and the bell hub stay in sync. Keyed by brief id. -------------------------- */
const ARCHIVE_KEY = "yoshi_archived_briefs";
const readArchived = () => { try { return JSON.parse(localStorage.getItem(ARCHIVE_KEY) || "[]"); } catch (e) { return []; } };
const writeArchived = (arr) => { try { localStorage.setItem(ARCHIVE_KEY, JSON.stringify(arr)); } catch (e) {} window.dispatchEvent(new CustomEvent("yoshi-archived")); };
const useArchivedBriefs = () => {
  const [arch, setArch] = useState(readArchived);
  useEffect(() => {
    const f = () => setArch(readArchived());
    window.addEventListener("yoshi-archived", f);
    return () => window.removeEventListener("yoshi-archived", f);
  }, []);
  const toggle = (id) => { const cur = readArchived(); writeArchived(cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id]); };
  const add = (id) => { const cur = readArchived(); if (!cur.includes(id)) writeArchived([...cur, id]); };
  return [arch, toggle, add];
};

/* ---- bell badge count · live unread "Needs you" proposals. The app shells
   (app.jsx / web.jsx) broadcast their current proposal count whenever the deck
   changes, so every bell — mobile NavBar and web top bar — stays in sync. ---- */
let __bellCount = (typeof PROPOSALS !== "undefined" ? PROPOSALS.length : 0);
const setBellCount = (n) => { __bellCount = n; window.dispatchEvent(new CustomEvent("yoshi-bell")); };
const useBellCount = () => {
  const [n, setN] = useState(__bellCount);
  useEffect(() => {
    const f = () => setN(__bellCount);
    window.addEventListener("yoshi-bell", f);
    return () => window.removeEventListener("yoshi-bell", f);
  }, []);
  return n;
};
window.setBellCount = setBellCount;
window.useBellCount = useBellCount;

/* hover-revealed definition for the row's category icon (alert vs insight).
   Replaces the native title= tooltip with a styled popover. */
const BRIEF_TIPS = {
  insight: ["Insight", "Something Yoshi noticed for you proactively, like portfolio concentration or spending patterns.", "var(--accent)"],
  alert: ["Alert", "Something Yoshi wants to tell you about, like a price alert you set, market recap, or scheduled reports.", "var(--ink-2)"],
  done: ["Completed", "A standing automation that already ran. Nothing here needs a decision.", "var(--accent-pos)"]
};
const IconTip = ({ kind, children }) => {
  const [show, setShow] = useState(false);
  const def = BRIEF_TIPS[kind] || BRIEF_TIPS.alert;
  return (
    <span
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
      onClick={(e) => e.stopPropagation()}
      style={{ width: 30, height: 30, flex: "none", display: "grid", placeItems: "center", marginTop: 1, position: "relative", color: def[2] }}>
      {children}
      {show && (
        <div style={{
          position: "absolute", top: "calc(100% + 6px)", left: -2, zIndex: 40, width: 196,
          background: "var(--ink)", color: "var(--bg)", padding: "9px 11px", borderRadius: 9,
          boxShadow: "0 6px 22px rgba(0,0,0,0.22)", pointerEvents: "none"
        }}>
          <div style={{ position: "absolute", top: -4, left: 12, width: 8, height: 8, background: "var(--ink)", transform: "rotate(45deg)" }} />
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", opacity: 0.65 }}>{def[0]}</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), lineHeight: 1.4, marginTop: 3 }}>{def[1]}</div>
        </div>
      )}
    </span>
  );
};

/* leading row icon — mirrors home's recent-activity RowIcon: Yoshi mark for
   agent-generated briefs (insights / approvals), schematic glyph otherwise. */
/* row identity — WHO sent this brief. Yoshi briefs (incl. rule-fired) carry
   the Yoshi glyph; external agents carry their own brand mark (ChatGPT,
   Claude, …), recolored to ink like the agents list. */
const BriefRowIcon = ({ icon, agent }) => {
  const yoshi = icon === "bolt";
  const p = useTheme();
  const brand = agent && typeof AGENT_ICON !== "undefined" && AGENT_ICON[agent];
  return (
    <span style={{ width: 24, height: 24, flex: "none", display: "grid", placeItems: "center", color: "var(--ink-2)", opacity: yoshi && p === "graphite" ? 0.72 : 1 }}>
      {brand ? <img src={brand} alt="" style={{ width: 16, height: 16, objectFit: "contain", filter: p === "bone" ? "brightness(0)" : "brightness(0) invert(1)", opacity: 0.85 }} />
        : yoshi ? <Logo size={15} /> : <Icon name={icon === "bulb" ? "trade" : (icon || "bell")} size={17} stroke={1.5} color="var(--ink-2)" />}
    </span>);
};

const BriefRow = ({ b, last, onOpen, selected }) =>
<button className="press" onClick={() => onOpen(b)}
style={{
  width: "100%", textAlign: "left", background: selected ? "var(--bg-2)" : "var(--bg)", border: "none",
  display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 12, alignItems: "center",
  padding: "13px 18px", cursor: "pointer", position: "relative"
}}>
    {selected && <span style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: 3, background: "var(--accent)" }} />}
    <BriefRowIcon icon={b.icon} agent={b.agentIcon} />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{b.title}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 14, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{b.body}</div>
    </div>
    {/* right side reads like an inbox: the date it came in — or the time,
        if today. Amounts and senders live in the body and the row icon. */}
    <div style={{ textAlign: "right", whiteSpace: "nowrap", fontFamily: "var(--f-mono)", fontSize: 12, color: "var(--ink-3)" }}>{b.when || ""}</div>
    {!last && <span style={{ position: "absolute", left: 18, right: 18, bottom: 0, height: 1, background: "var(--rule)" }} />}
  </button>;


/* swipe a row left to archive (or restore, when already archived) */
const SwipeRow = ({ archived, onAction, last, children, label, icon, bg }) => {
  const [dx, setDx] = useState(0);
  const startX = useRef(null);
  const moved = useRef(false);
  const lbl = label || (archived ? "Restore" : "Archive");
  const ic = icon || (archived ? "back" : "inbox");
  const back = bg || (archived ? "var(--accent-pos)" : "var(--ink)");
  const onDown = (e) => { startX.current = e.clientX; moved.current = false; };
  const onMove = (e) => {
    if (startX.current == null) return;
    let d = e.clientX - startX.current;
    if (Math.abs(d) > 4) moved.current = true;
    setDx(Math.max(-112, Math.min(0, d)));
  };
  const end = () => {
    if (startX.current == null) return;
    const fire = dx <= -56;
    startX.current = null; setDx(0);
    if (fire) onAction();
  };
  return (
    <div style={{ position: "relative", overflow: "hidden", background: "var(--bg)" }}>
      {dx < 0 &&
      <div style={{ position: "absolute", inset: 0, display: "flex", justifyContent: "flex-end", alignItems: "center", paddingRight: 22, background: back }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color: "var(--bg)", fontFamily: "var(--f-display)", fontSize: typeSize(12), fontWeight: 500 }}>
          <Icon name={ic} size={15} color="var(--bg)" />{lbl}
        </span>
      </div>}
      <div onPointerDown={onDown} onPointerMove={onMove} onPointerUp={end} onPointerLeave={end} onPointerCancel={end}
        onClickCapture={(e) => { if (moved.current) { e.stopPropagation(); e.preventDefault(); } }}
        style={{ position: "relative", background: "var(--bg)", transform: `translateX(${dx}px)`, transition: startX.current == null ? "transform 200ms cubic-bezier(0.16,1,0.3,1)" : "none", touchAction: "pan-y" }}>
        {children}
      </div>
    </div>
  );
};


const BRIEF_TYPE = { recap: "Market recap", alert: "Price alert", opportunity: "Opportunity", nudge: "Heads-up", earnings: "Earnings tomorrow", automation: "Automation", report: "Scheduled report" };

const PortfolioUpdateDailyWeb = ({ update }) => (
  <>
    <p style={{ maxWidth: 680, fontFamily: "var(--f-display)", fontSize: typeSize(15), lineHeight: 1.6, color: "var(--ink)", margin: 0 }}>{update.opening}</p>
    <section style={{ marginTop: 26 }}>
      <Eyebrow style={{ marginBottom: 9 }}>News and drivers</Eyebrow>
      <div style={{ width: "min(100%, 680px)", overflow: "hidden", border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)" }}>
        {update.drivers.map(({ performance: [ticker, name, change, percent], news }, index) => <div key={ticker} style={{ padding: "15px 16px", borderTop: index ? "1px solid var(--rule)" : "none" }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 16 }}>
            <div><span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), fontWeight: 500 }}>{ticker}</span><span style={{ marginLeft: 7, fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)" }}>{name}</span></div>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12), color: change.startsWith("−") ? "var(--signal-neg)" : "var(--accent-pos)", whiteSpace: "nowrap" }}>{change} ({percent})</span>
          </div>
          <div style={{ marginTop: 7, maxWidth: 610, fontFamily: "var(--f-display)", fontSize: typeSize(13), lineHeight: 1.5, color: "var(--ink-2)" }}>{news}</div>
        </div>)}
      </div>
    </section>
    <section style={{ marginTop: 25 }}>
      <Eyebrow style={{ marginBottom: 9 }}>Worth knowing</Eyebrow>
      <div style={{ display: "flex", flexDirection: "column", gap: 10, maxWidth: 680 }}>{update.takeaways.map((item) => <div key={item} style={{ display: "flex", gap: 9, alignItems: "flex-start" }}><span style={{ width: 5, height: 5, borderRadius: 999, flex: "none", marginTop: 8, background: "var(--accent)" }} /><div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.55, color: "var(--ink-2)" }}>{item}</div></div>)}</div>
    </section>
    <div style={{ maxWidth: 680, marginTop: 26, paddingTop: 20, borderTop: "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.55, color: "var(--ink)" }}>{update.cadenceNote}</div>
  </>
);

const PROPOSAL_LEG_COLUMNS = "64px minmax(0, 1fr)";
const isSecurityLegLabel = (label) => label && typeof label === "object" && label.ticker;

const SecurityLine = ({ security }) =>
  <div style={{ minWidth: 0 }}>
    <div style={{ display: "flex", alignItems: "baseline", gap: 6, flexWrap: "wrap" }}>
      <span style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", fontWeight: 500, color: "var(--ink)" }}>{security.ticker}</span>
      <span style={{ fontFamily: "var(--f-mono)", fontSize: "var(--ld-type-small)", color: "var(--ink)", fontVariantNumeric: "proportional-nums" }}>{security.price}</span>
      <span style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", color: security.changeTone === "neg" ? "var(--signal-neg)" : "var(--accent-pos)", whiteSpace: "nowrap" }}>{security.change}</span>
    </div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", color: "var(--ink-3)", marginTop: 2 }}>{security.name} · {security.valueLabel}</div>
  </div>;

const ProposalLegContent = ({ label, detail, amount }) => {
  const securityLabel = isSecurityLegLabel(label);
  return (
    <div style={{ minWidth: 0 }}>
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 10, minWidth: 0 }}>
        <div style={{ minWidth: 0, flex: "1 1 auto" }}>
          {securityLabel ? <SecurityLine security={label} /> : <>
            <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", fontWeight: 500 }}>{label}</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-small)", color: "var(--ink-2)", marginTop: 4, lineHeight: 1.45 }}>{detail}</div>
          </>}
        </div>
        {amount && <span style={{ flex: "0 0 auto", fontFamily: "var(--f-mono)", fontSize: "var(--ld-type-body)", color: "var(--ink)", whiteSpace: "nowrap", textAlign: "right", fontVariantNumeric: "proportional-nums", paddingTop: 1 }}>{amount}</span>}
      </div>
    </div>);
};

const proposalAmountLabel = (p) => p.amountLabel || (p.kind === "trade" ? "Trade amount" : "Transfer amount");
const proposalAmountValue = (p) => p.amount || usd(Math.abs(p.net || 0), 2);
const proposalCashImpactValue = (p) => p && p.kind === "trade" ? p.cashImpact : null;
const ProposalAmountSummary = ({ p }) => {
  const cashImpact = proposalCashImpactValue(p);
  return (
    <div style={{ marginTop: 13, display: "grid", gap: 7 }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
        <span style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", fontWeight: 500, letterSpacing: "normal",  color: "var(--accent)" }}>{proposalAmountLabel(p)}</span>
        <span style={{ fontFamily: "var(--f-mono)", fontSize: "var(--ld-type-value)", color: "var(--ink)", fontVariantNumeric: "proportional-nums" }}>{proposalAmountValue(p)}</span>
      </div>
      {cashImpact && <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
        <span style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", fontWeight: 500, letterSpacing: "normal",  color: "var(--accent)" }}>{p.cashImpactLabel || "Cash impact"}</span>
        <span style={{ fontFamily: "var(--f-mono)", fontSize: "var(--ld-type-body)", color: cashImpact.startsWith("+") ? "var(--accent-pos)" : cashImpact.startsWith("-") ? "var(--signal-neg)" : "var(--ink)", fontVariantNumeric: "proportional-nums" }}>{cashImpact}</span>
      </div>}
    </div>);
};

const metricTone = (value) => {
  const text = String(value || "");
  if (text.startsWith("+")) return "var(--accent-pos)";
  if (text.startsWith("-") || text.startsWith("−")) return "var(--signal-neg)";
  return "var(--ink)";
};

const InsightMetricGrid = ({ metrics = [] }) => metrics.length ? (
  <div style={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", gap: 1, border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", marginTop: 10 }}>
    {metrics.map(([label, value, note]) => (
      <div key={label} style={{ minWidth: 0, background: "var(--bg-card)", padding: "9px 10px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(9.5), color: "var(--ink-3)", marginBottom: 4 }}>{label}</div>
        <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), color: metricTone(value), fontVariantNumeric: "proportional-nums", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{value}</div>
        {note && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(9.5), color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{note}</div>}
      </div>
    ))}
  </div>
) : null;

const ShadcnChartContainer = ({ type, children, style }) => (
  <div data-chart-container="shadcn" data-chart={type} style={{ marginTop: 10, ...style }}>
    {children}
  </div>
);

const ShadcnAreaChart = ({ points = [] }) => {
  if (!points.length) return null;
  const min = Math.min(...points);
  const max = Math.max(...points);
  const span = max - min || 1;
  const W = 260;
  const H = 82;
  const padX = 4;
  const padTop = 8;
  const padBottom = 18;
  const innerW = W - padX * 2;
  const innerH = H - padTop - padBottom;
  const coords = points.map((value, i) => {
    const x = padX + (points.length === 1 ? innerW : i / (points.length - 1) * innerW);
    const y = padTop + innerH - ((value - min) / span) * innerH;
    return [x, y];
  });
  const line = coords.map(([x, y], i) => `${i === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}`).join(" ");
  const area = `${line} L ${coords[coords.length - 1][0].toFixed(2)} ${H - padBottom} L ${coords[0][0].toFixed(2)} ${H - padBottom} Z`;
  const gradientId = `mobile-insight-area-${points.length}-${Math.round(max * 100)}`;
  return (
    <ShadcnChartContainer type="area">
      <svg data-insight-graph="shadcn-area" role="img" aria-label="Value" viewBox={`0 0 ${W} ${H}`} width="100%" height="82" preserveAspectRatio="none" style={{ display: "block" }}>
        <defs>
          <linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
            <stop offset="5%" stopColor="var(--accent)" stopOpacity="0.34" />
            <stop offset="95%" stopColor="var(--accent)" stopOpacity="0.04" />
          </linearGradient>
        </defs>
        {[0.25, 0.5, 0.75].map((n) => <line key={n} x1={padX} x2={W - padX} y1={padTop + innerH * n} y2={padTop + innerH * n} stroke="var(--rule)" strokeWidth="1" vectorEffect="non-scaling-stroke" />)}
        <path d={area} fill={`url(#${gradientId})`} />
        <path d={line} fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
        <circle cx={coords[coords.length - 1][0]} cy={coords[coords.length - 1][1]} r="3" fill="var(--accent-pos)" />
      </svg>
    </ShadcnChartContainer>
  );
};

const signedBarValue = (bar) => {
  const display = String(bar.display || "").trim();
  const sign = display.startsWith("-") || display.startsWith("−") ? -1 : 1;
  const numeric = typeof bar.value === "number" ? bar.value : Number(display.replace(/[^0-9.]/g, ""));
  return sign * Math.abs(Number.isFinite(numeric) ? numeric : 0);
};

const ShadcnBarChart = ({ bars = [] }) => {
  if (!bars.length) return null;
  const chartData = bars.map((bar) => ({ ...bar, amount: signedBarValue(bar) }));
  const min = Math.min(0, ...chartData.map((bar) => bar.amount));
  const max = Math.max(0, ...chartData.map((bar) => bar.amount));
  const span = max - min || 1;
  const W = 300;
  const H = 126;
  const padX = 18;
  const padTop = 14;
  const padBottom = 32;
  const innerW = W - padX * 2;
  const innerH = H - padTop - padBottom;
  const xStep = innerW / chartData.length;
  const barW = Math.min(38, xStep * 0.56);
  const yFor = (value) => padTop + ((max - value) / span) * innerH;
  const zeroY = yFor(0);
  return (
    <ShadcnChartContainer type="bar">
      <svg data-insight-graph="shadcn-bar" role="img" aria-label="Amount" viewBox={`0 0 ${W} ${H}`} width="100%" height="132" preserveAspectRatio="xMidYMid meet" style={{ display: "block" }}>
        {[0.25, 0.5, 0.75].map((n) => <line key={n} x1={padX} x2={W - padX} y1={padTop + innerH * n} y2={padTop + innerH * n} stroke="var(--rule)" strokeWidth="1" />)}
        <line x1={padX} x2={W - padX} y1={zeroY} y2={zeroY} stroke="var(--rule-2)" strokeWidth="1.2" />
        {chartData.map((bar, i) => {
          const x = padX + i * xStep + (xStep - barW) / 2;
          const y = yFor(bar.amount);
          const h = Math.max(2, Math.abs(zeroY - y));
          const top = Math.min(y, zeroY);
          const labelY = bar.amount < 0 ? Math.min(H - 40, zeroY + h + 12) : Math.max(11, top - 6);
          return (
            <g key={bar.label}>
              <rect x={x} y={top} width={barW} height={h} rx="5" fill={bar.amount < 0 ? "var(--signal-neg)" : "var(--accent-pos)"} opacity="0.88" />
              <text x={x + barW / 2} y={labelY} textAnchor="middle" fill={metricTone(bar.display)} style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, fontVariantNumeric: "proportional-nums" }}>{bar.display}</text>
              <text x={x + barW / 2} y={H - 11} textAnchor="middle" fill="var(--ink-3)" style={{ fontFamily: "var(--f-display)", fontSize: 9.5 }}>{bar.label}</text>
            </g>
          );
        })}
      </svg>
    </ShadcnChartContainer>
  );
};

const INSIGHT_EVIDENCE_SIDE_GUTTER = 14;
const InsightEvidence = ({ visual }) => {
  if (!visual) return null;
  return (
    <div data-insight-evidence style={{
      width: `calc(100% - ${INSIGHT_EVIDENCE_SIDE_GUTTER * 2}px)`,
      maxWidth: 520,
      boxSizing: "border-box",
      margin: "16px auto 0",
      padding: 12,
      border: "1px solid var(--rule)",
      borderRadius: 12,
      background: "var(--bg-card)"
    }}>
      <Eyebrow style={{ marginBottom: 9 }}>{visual.title || "Evidence"}</Eyebrow>
      {visual.kind === "spark" && <ShadcnAreaChart points={visual.points} />}
      {visual.kind === "bars" && <ShadcnBarChart bars={visual.bars} />}
      <InsightMetricGrid metrics={visual.metrics} />
    </div>
  );
};

const HoldingEventBrief = ({ event }) => (
  <div style={{ width: "100%", marginTop: 24 }}>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.48, letterSpacing: "normal", color: "var(--ink)" }}>{event.driver}</div>
    {event.actionPrompt && <>
      <div style={{ marginTop: 28, fontFamily: "var(--f-display)", fontSize: typeSize(15), lineHeight: 1.5, color: "var(--ink)" }}>{event.actionPrompt}</div>
    </>}
    {event.gainLoss && <section style={{ marginTop: 28 }}>
      <Eyebrow style={{ marginBottom: 8 }}>{event.gainLossTitle}</Eyebrow>
      <div style={{ borderTop: "1px solid var(--rule)", borderBottom: "1px solid var(--rule)" }}>
        {event.gainLoss.map(([label, value], index) => <div key={label} style={{ display: "flex", justifyContent: "space-between", gap: 20, padding: "11px 0", borderBottom: index === event.gainLoss.length - 1 ? "none" : "1px dashed var(--rule)" }}><span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), color: "var(--ink-2)" }}>{label}</span><span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(13), color: value.startsWith("−") ? "var(--signal-neg)" : "var(--accent-pos)" }}>{value}</span></div>)}
      </div>
    </section>}
    {event.ongoingNote && <div style={{ marginTop: 28, fontFamily: "var(--f-display)", fontSize: typeSize(13), lineHeight: 1.5, color: "var(--ink-2)" }}>{event.ongoingNote}</div>}
  </div>
);

const EarningsEventCardWeb = ({ label, metrics, comparison = false, performance = false }) => <section>
  <div><Eyebrow>{label}</Eyebrow></div>
  <div style={{ position: "relative", maxWidth: 500, margin: "10px auto 0", padding: "16px 22px 18px", border: "1px solid var(--rule)", borderRadius: 12, background: "var(--bg-card)" }}>
    <span aria-hidden="true" style={{ position: "absolute", top: 18, bottom: 18, left: "50%", borderLeft: "1px dashed var(--rule-2)" }} />
    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
      {metrics.map(([metricLabel, value, note]) => {
        const parts = comparison ? note.match(/^(.*?)(\([+-][^)]*%\))$/) : performance ? note.match(/^(.*?)( year over year)$/) : null;
        const variance = parts?.[2];
        const varianceColor = variance?.startsWith("(+") ? "var(--accent-pos)" : variance?.startsWith("(-") ? "var(--signal-neg)" : "var(--ink)";
        return <div key={metricLabel} style={{ textAlign: "center" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), color: "var(--ink-3)" }}>{metricLabel}</div>
          <div style={{ marginTop: 3, fontFamily: "var(--f-mono)", fontSize: typeSize(18) }}>{value}</div>
          <div style={{ marginTop: 3, fontFamily: "var(--f-display)", fontSize: typeSize(11), color: comparison || performance ? "var(--ink-2)" : "var(--ink)" }}>{performance && parts ? <><span style={{ color: "var(--accent-pos)" }}>{parts[1]}</span>{parts[2]}</> : <>{parts?.[1] || note}{variance && <span style={{ color: varianceColor }}>{variance}</span>}</>}</div>
        </div>;
      })}
    </div>
  </div>
</section>;

const InvestorProfileBriefWeb = ({ profile, opening }) => {
  const [answers, setAnswers] = useState({});
  const selectAnswer = (questionId, option) => setAnswers((current) => ({ ...current, [questionId]: option }));
  return <div style={{ width: "100%", maxWidth: 720 }}>
    <p style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.6, color: "var(--ink)", margin: 0 }}>{opening}</p>

    {profile.known.length > 0 && <section style={{ marginTop: 30 }}>
      <Eyebrow style={{ marginBottom: 10 }}>What I already know</Eyebrow>
      <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
        {profile.known.map(([label, value], index) => <div key={label} style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 18, padding: "14px 16px", borderTop: index ? "1px solid var(--rule)" : "none" }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-3)" }}>{label}</span>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 500, color: "var(--ink)", textAlign: "right" }}>{value}</span>
        </div>)}
      </div>
      <div style={{ marginTop: 9, fontFamily: "var(--f-display)", fontSize: typeSize(12), lineHeight: 1.5, color: "var(--ink-3)" }}>{profile.knownNote}</div>
    </section>}

    <section style={{ marginTop: 30 }}>
      <Eyebrow style={{ marginBottom: 5 }}>What I still need</Eyebrow>
      {profile.questions.map((question, questionIndex) => <div key={question.id} style={{ paddingTop: 18, marginTop: questionIndex ? 20 : 0, borderTop: questionIndex ? "1px solid var(--rule)" : "none" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(15), fontWeight: 500, lineHeight: 1.45, color: "var(--ink)" }}>{question.prompt}</div>
        <div role="group" aria-label={question.prompt} style={{ display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", gap: 10, marginTop: 12 }}>
          {question.options.map((option) => {
            const selected = answers[question.id] === option;
            return <button key={option} type="button" className="press" aria-pressed={selected} onClick={() => selectAnswer(question.id, option)} style={{ minHeight: 48, padding: "10px 14px", border: `1px solid ${selected ? "var(--accent)" : "var(--rule-2)"}`, borderRadius: 10, background: selected ? "color-mix(in srgb, var(--accent) 14%, var(--bg-card))" : "var(--bg-card)", color: "var(--ink)", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: selected ? 650 : 500, lineHeight: 1.35, textAlign: "center" }}>{option}</button>;
          })}
        </div>
      </div>)}
    </section>

    <div style={{ marginTop: 30, paddingTop: 22, borderTop: "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.55, color: "var(--ink-2)" }}>{profile.optionalPrompt}</div>
  </div>;
};

const PaydayBriefWeb = ({ payday, onOpenAutomation }) => (
  <div style={{ width: "100%", marginTop: 28, paddingBottom: 32 }}>
    {payday.automations.length > 0 && <section>
      <Eyebrow style={{ marginBottom: 10 }}>Yoshi at work</Eyebrow>
      <div style={{ display: "flex", flexDirection: "column", maxWidth: 560, border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
        {payday.automations.map((item, index) => <button key={item.automationId} className="press" onClick={() => onOpenAutomation(item.automationId)} style={{ display: "flex", alignItems: "center", gap: 12, width: "100%", padding: "14px 16px", border: "none", borderTop: index ? "1px solid var(--rule)" : "none", background: "transparent", color: "var(--ink)", textAlign: "left", cursor: "pointer" }}>
          <span style={{ width: 7, height: 7, borderRadius: 999, flex: "none", background: "var(--accent-pos)" }} />
          <span style={{ minWidth: 0, flex: 1 }}>
            <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 500 }}>{item.title}</span>
            <span style={{ display: "block", marginTop: 3, fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)" }}>{item.outcome}</span>
          </span>
          <Icon name="back" size={14} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
        </button>)}
      </div>
    </section>}
    <div style={{ marginTop: payday.automations.length > 0 ? 28 : 0, paddingTop: 20, borderTop: "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.55, color: "var(--ink)" }}>{payday.closing}</div>
  </div>
);

const DebtOverviewBriefWeb = ({ overview }) => {
  const verificationDebt = overview.debts.find((debt) => debt.verification);
  return <div style={{ width: "100%", marginTop: 30, paddingBottom: 42 }}>
    <p style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.55, color: "var(--ink)", margin: 0 }}>{overview.intro}</p>
    <p style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.55, color: "var(--ink)", margin: "14px 0 0" }}>{overview.analysis}</p>
    <section style={{ marginTop: 30 }}>
      <Eyebrow style={{ marginBottom: 10 }}>Credit profile</Eyebrow>
      <InsightMetricGrid metrics={overview.creditProfile} />
      <div style={{ marginTop: 12, padding: "14px 16px", border: "1px solid color-mix(in srgb, var(--accent-pos) 44%, var(--rule))", borderRadius: 12, background: "color-mix(in srgb, var(--accent-pos) 7%, var(--bg-card))", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.55, color: "var(--ink-2)" }}>{overview.creditSummary}</div>
    </section>
    <section style={{ marginTop: 30 }}>
      <Eyebrow style={{ marginBottom: 10 }}>Your debts</Eyebrow>
      <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
        {overview.debts.map((debt, index) => <div key={`${debt.name}-${debt.mask}`} style={{ padding: "15px 16px", borderTop: index ? "1px solid var(--rule)" : "none" }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 16 }}>
            <div style={{ minWidth: 0 }}><span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500 }}>{debt.name}</span><span style={{ marginLeft: 6, fontFamily: "var(--f-mono)", fontSize: typeSize(11), color: "var(--ink-3)" }}>{debt.mask}</span></div>
            <span style={{ flex: "none", fontFamily: "var(--f-mono)", fontSize: typeSize(14), fontVariantNumeric: "proportional-nums" }}>{debt.balance}</span>
          </div>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginTop: 7 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-2)" }}>{debt.apr}</span>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)" }}>{debt.minimum}</span>
          </div>
          <div style={{ marginTop: 6, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--accent-pos)" }}>{debt.note}</span>
            {debt.verification && <AccountConnectionPill action={debt.verification} onClick={() => {}} />}
          </div>
        </div>)}
      </div>
    </section>
    <section style={{ marginTop: 30, paddingTop: 22, borderTop: "1px solid var(--rule)" }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.55, color: "var(--ink)" }}>{overview.actionPrompt}</div>
      {verificationDebt && <Btn full style={{ marginTop: 16 }}>{verificationDebt.verification.label}</Btn>}
      <div style={{ marginTop: 10, display: "flex", gap: 8, alignItems: "flex-start", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), lineHeight: 1.45, color: "var(--ink-3)" }}><Icon name="shield" size={14} color="var(--ink-3)" style={{ flex: "none", marginTop: 1 }} /><span>{overview.actionBoundary}</span></div>
    </section>
  </div>;
};

const EarningsPreWeb = ({ earnings, opening }) => {
  return <div style={{ width: "100%", marginTop: 48, paddingBottom: 48 }}>
    {opening && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.55, color: "var(--ink)" }}>{opening}</div>}
    <div style={{ display: "flex", flexDirection: "column", gap: 28, marginTop: opening ? 34 : 0 }}>
      <EarningsEventCardWeb label="Consensus expectations" metrics={earnings.consensus} performance />
    </div>

    <section style={{ marginTop: 34 }}>
      <Eyebrow>What to watch</Eyebrow>
      <div style={{ display: "flex", flexDirection: "column", gap: 9, marginTop: 10 }}>
        {earnings.watch.map((item) => <div key={item} style={{ display: "flex", gap: 9, alignItems: "flex-start" }}>
          <span style={{ width: 5, height: 5, borderRadius: 999, background: "var(--accent)", marginTop: 7, flex: "none", opacity: .75 }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.5, color: "var(--ink)" }}>{item}</span>
        </div>)}
      </div>
    </section>

    <section style={{ marginTop: 30, padding: "18px 20px", borderRadius: 12, background: "var(--bg-2)", border: "1px solid var(--rule)" }}>
      <Eyebrow>Recent news</Eyebrow>
      <div style={{ marginTop: 6, fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.5, color: "var(--ink-2)" }}>{earnings.news}</div>
    </section>

    <section style={{ marginTop: 28, fontFamily: "var(--f-display)", fontSize: typeSize(13), lineHeight: 1.5, color: "var(--ink-2)" }}>Let me know if you don't want me to send you these going forward.</section>
  </div>;
};

const EarningsPostWeb = ({ earnings, opening, onAsk }) => {
  const isExample = !!earnings.marketExample;
  return <div style={{ width: "100%", marginTop: 48, paddingBottom: 48 }}>
    {opening && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), lineHeight: 1.55, color: "var(--ink)" }}>{opening}</div>}
    <div style={{ display: "flex", flexDirection: "column", gap: 28, marginTop: opening ? 34 : 0 }}>
      <EarningsEventCardWeb label="Results vs expectations" metrics={earnings.results} comparison />
    </div>

    <section style={{ marginTop: 34 }}>
      <Eyebrow>Key takeaways</Eyebrow>
      <div style={{ display: "flex", flexDirection: "column", gap: 9, marginTop: 10 }}>
        {earnings.takeaways.map((item) => <div key={item} style={{ display: "flex", gap: 9, alignItems: "flex-start" }}>
          <span style={{ width: 5, height: 5, borderRadius: 999, background: "var(--accent)", marginTop: 7, flex: "none", opacity: .75 }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.5, color: "var(--ink)" }}>{item}</span>
        </div>)}
      </div>
    </section>

    {!isExample && <section style={{ marginTop: 28, fontFamily: "var(--f-display)", fontSize: typeSize(13), lineHeight: 1.5, color: "var(--ink-2)" }}>Let me know if you don't want me to send you these in the future.</section>}
    {isExample && <div style={{ marginTop: 28 }}><Btn full onClick={() => onAsk && onAsk("Help me connect or add my portfolio so Yoshi can brief me on my holdings.")}>Connect or add a portfolio</Btn></div>}
  </div>;
};

/* a single brief, opened like an email — full read + a chat-with-Yoshi CTA */
/* the ordered "catch up" feed (error alerts, then insights) — matches the
   openable order in BriefsBoard so Mark-as-read advances brief→brief */
const BRIEF_FEED = () => [
  ...ERROR_ALERTS.map((e) => ({ ...e, kind: "alert", icon: "bell" })),
  ...BRIEF_INSIGHTS.map((it) => ({ id: "ins-" + it.label, kind: "insight", icon: "bulb", title: it.label, body: it.read, ask: it.ask, stat: it.stat, tone: it.tone, visual: it.visual, detail: it.detail, when: it.when, ts: it.ts })),
  ...BRIEFS.filter((b) => b.folder === "insights" && (!b.automationMoveReview || window.AutomationReview.eligible(b.automationMoveReview).length > 0)).map((b) => ({ ...b, kind: "insight", icon: "bulb" })),
].sort((a, b) => (b.ts || 0) - (a.ts || 0));

/* per-brief conversation · chatting inside an alert/insight brief keeps you in
   the brief (not the global chat) and persists, so returning shows the context. */
const transientBriefThreads = {};
const BRIEF_THREAD_KEY = "yoshi_brief_threads";
const readBriefThreads = () => { try { return JSON.parse(localStorage.getItem(BRIEF_THREAD_KEY) || "{}"); } catch (e) { return {}; } };
const clearBriefThread = (id) => {
  const all = readBriefThreads();
  if (!(id in all)) return;
  delete all[id];
  try { localStorage.setItem(BRIEF_THREAD_KEY, JSON.stringify(all)); } catch (e) {}
};
const useBriefThread = (id, shouldPersist = true) => {
  const [msgs, setMsgs] = useState(() => shouldPersist ? readBriefThreads()[id] || [] : transientBriefThreads[id] || []);
  useEffect(() => {
    if (!shouldPersist) { clearBriefThread(id); setMsgs(transientBriefThreads[id] || []); return; }
    setMsgs(readBriefThreads()[id] || []);
  }, [id, shouldPersist]);
  const persist = (next) => {
    if (shouldPersist) { const all = readBriefThreads(); all[id] = next; try { localStorage.setItem(BRIEF_THREAD_KEY, JSON.stringify(all)); } catch (e) {} }
    else transientBriefThreads[id] = next;
    setMsgs(next);
  };
  return [msgs, persist];
};
const briefFollowup = (t) => {
  const s = (t || "").toLowerCase();
  if (/why|how|explain|what/.test(s)) return "Good question. The short version: it follows from your current balances and the rule you set. I can show the exact numbers or draft a change if you'd like.";
  if (/yes|do it|go ahead|sure|please/.test(s)) return "On it. I'll put together a proposal you can approve, and flag it in your stream before anything runs.";
  return "Got it. I'll look into that and come back with something specific you can act on.";
};
const BRIEF_REVISION_RE = /(make it|smaller|less|half|reduce|lower|safer|safest|trim|only the|wait|push|friday|monday|instead|resize|re-?size|revise|adjust|change it)/i;
const briefProposalReply = (text, revised) => {
  if (revised) return `Done. ${revised.amount ? `I updated it to ${revised.amount}. ` : ""}Review the revised details above, then confirm with passkey or decline it here.`;
  if (window.threadReply) return window.threadReply(text).t;
  return "Got it. I can resize it, push the timing, or keep only the safest leg. Tell me which and I'll update this brief.";
};

const BriefBubble = ({ m }) => {
  const isUser = m.from === "user";
  return (
    <div className="yo-enter" style={{ display: "flex", justifyContent: isUser ? "flex-end" : "flex-start" }}>
      <div style={{ maxWidth: "84%", padding: "9px 12px", borderRadius: isUser ? "13px 13px 4px 13px" : "13px 13px 13px 4px", background: isUser ? "var(--accent)" : "var(--bg-card)", color: isUser ? "var(--accent-ink)" : "var(--ink)", border: isUser ? "none" : "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.45 }}>{m.t}</div>
    </div>);
};
const BriefTyping = () =>
<div style={{ display: "flex", justifyContent: "flex-start" }}>
    <div style={{ padding: "11px 13px", background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: "13px 13px 13px 4px", display: "flex", gap: 4 }}>
      {[0, 1, 2].map((i) => <span key={i} className="yo-pulse" style={{ width: 6, height: 6, borderRadius: 999, background: "var(--ink-3)", animationDelay: `${i * 200}ms` }} />)}
    </div>
  </div>;

const AutomationRevisionProposalTile = ({ proposal, onReview, onDiscard }) => {
  return <div className="yo-enter" style={{ alignSelf: "flex-start", width: "min(100%, 300px)", padding: "11px 12px 12px", border: "1px solid var(--accent)", borderRadius: 12, background: "var(--bg-card)" }}>
    <Eyebrow color="var(--accent)">Needs you</Eyebrow>
    <div style={{ marginTop: 5, fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, letterSpacing: "normal" }}>Review revised {proposal.ticker} automation</div>
    <div style={{ marginTop: 4, fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)", lineHeight: 1.45 }}>{proposal.label}</div>
    <Btn full size="sm" onClick={() => onReview && onReview(proposal)} style={{ marginTop: 10 }}>Review proposal</Btn>
    <button onClick={onDiscard} style={{ marginTop: 8, padding: "6px 0", background: "none", border: "none", color: "var(--ink-2)", font: "inherit", fontSize: typeSize(12), cursor: "pointer" }}>Discard proposal</button>
  </div>;
};

const StatusTag = ({ label, color }) =>
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "4px 10px", borderRadius: 999, border: `1px solid ${color}`, color, fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", whiteSpace: "nowrap", flex: "none" }}>
    <span style={{ width: 5, height: 5, borderRadius: 999, background: color }} /> {label}
  </span>;

const openRetryDestination = (retry, nav, closeBrief) => {
  if (!retry || !nav) return false;
  const dest = retry.destination;
  if (!dest) return false;
  closeBrief && closeBrief();
  if (dest.type === "trade" && nav.sheet) {
    nav.sheet({ type: "trade", id: dest.id, side: dest.side, source: retry });
    return true;
  }
  if (dest.type === "transfer") {
    if (nav.transfer) {
      nav.transfer({ intent: dest.intent, from: dest.from, rail: dest.rail, retry });
      return true;
    }
    if (nav.sheet) {
      nav.sheet({ type: "transfer", intent: dest.intent, from: dest.from, rail: dest.rail, retry });
      return true;
    }
  }
  if (dest.type === "automation") {
    if (nav.studio) {
      nav.studio(dest.view || "automations");
      return true;
    }
    if (nav.automation) {
      nav.automation(dest.id);
      return true;
    }
  }
  return false;
};

const briefQueuesForOpen = (brief) => !MARKET_OPEN && (
  brief.proposal && brief.proposal.kind === "trade" ||
  (brief.series || []).some((row) => ["BUY", "SELL"].includes(row.tag))
);
const BriefClosedMarketStatus = () => (
  <div data-brief-market-status="closed" style={{ display: "flex", alignItems: "flex-start", gap: 9, marginTop: 16, padding: "11px 12px", background: "color-mix(in srgb, var(--accent) 8%, var(--bg-card))", border: "1px solid var(--accent)", borderRadius: 10 }}>
    <Icon name="clock" size={15} color="var(--accent)" style={{ flex: "none", marginTop: 1 }} />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), fontWeight: 500, color: "var(--ink)" }}>Market closed · opens tomorrow 9:30 AM ET</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", lineHeight: 1.45, marginTop: 3 }}>This becomes a one-time automation and executes at market open. You can cancel before then.</div>
      <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(9), color: "var(--accent)", letterSpacing: "normal", textTransform: "uppercase", marginTop: 6 }}>One-time automation</div>
    </div>
  </div>
);

const ReviewCadenceChoice = ({ choices = [] }) => {
  const [selected, setSelected] = useState("Weekly");
  return (
    <div style={{ marginTop: 22 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), lineHeight: 1.45, color: "var(--ink-2)" }}>How often should Yoshi send a review like this?</div>
      <div style={{ display: "flex", gap: 7, flexWrap: "wrap", marginTop: 10 }}>
        {choices.map((choice) => <button key={choice} className="press" onClick={() => setSelected(choice)} style={{ border: `1px solid ${selected === choice ? "var(--accent)" : "var(--rule-2)"}`, borderRadius: 999, padding: "7px 10px", background: selected === choice ? "var(--accent)" : "var(--bg-2)", color: selected === choice ? "var(--accent-ink)" : "var(--ink)", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 500 }}>{choice}</button>)}
      </div>
      {selected && <div style={{ marginTop: 10, fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)", lineHeight: 1.45 }}>{selected === "Off" ? "Yoshi will stop these reviews. You can turn them back on anytime." : selected === "Action-only" ? "Yoshi will only reach out when something needs your attention." : `Yoshi will send a ${selected.toLowerCase()} review only when there is something meaningful to share.`}</div>}
    </div>
  );
};

const WeeklyReviewExplorer = ({ snapshot = [], attention, events = [], sectionLabel = "What changed", handled = [], marketContext, onAsk }) => {
  return (
    <div style={{ marginTop: 18 }}>
      <Eyebrow style={{ marginBottom: 9 }}>Your week in numbers</Eyebrow>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, 144px)", justifyContent: "center", gap: 8, width: "100%", margin: "0 auto" }}>
        {snapshot.map(([label, value, note, tone]) => (
          <div key={label} style={{ minWidth: 0, height: 96, padding: "13px 12px", borderRadius: 12, background: "var(--bg-card)", border: "1px solid var(--rule)", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", textAlign: "center" }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), color: "var(--ink-3)" }}>{label}</div>
            <div style={{ marginTop: 7, fontFamily: "var(--f-mono)", fontSize: typeSize(15), fontWeight: 500, color: tone === "pos" ? "var(--accent-pos)" : "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "100%" }}>{value}</div>
            <div style={{ marginTop: 5, fontFamily: "var(--f-display)", fontSize: typeSize(10), color: "var(--ink-3)", lineHeight: 1.3 }}>{note}</div>
          </div>
        ))}
      </div>
      {attention && <div style={{ marginTop: 22 }}>
        <Eyebrow style={{ marginBottom: 9 }}>What needs your attention</Eyebrow>
        <section style={{ padding: "15px", border: "1px solid var(--accent)", borderRadius: 12, background: "var(--bg-card)" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500 }}>{attention.title}</div>
          <div style={{ marginTop: 6, fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)", lineHeight: 1.5 }}>{attention.body}</div>
          <button className="press" onClick={() => onAsk && onAsk(attention.prompt)} style={{ marginTop: 12, border: "none", borderRadius: 9, padding: "9px 11px", background: "var(--accent)", color: "var(--accent-ink)", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 500 }}>{attention.cta}</button>
        </section>
      </div>}
      <section style={{ marginTop: 22 }}>
        <Eyebrow style={{ marginBottom: 9 }}>{sectionLabel}</Eyebrow>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {events.map((event, index) => <div key={event} style={{ display: "flex", gap: 9 }}><span style={{ flex: "none", width: 19, height: 19, borderRadius: 999, display: "grid", placeItems: "center", background: "var(--bg-2)", border: "1px solid var(--rule)", fontFamily: "var(--f-mono)", fontSize: typeSize(9), color: "var(--accent)" }}>{index + 1}</span><div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), lineHeight: 1.5, color: "var(--ink-2)" }}>{event}</div></div>)}
        </div>
      </section>
      {handled.length > 0 && <section style={{ marginTop: 22 }}>
        <Eyebrow style={{ marginBottom: 9 }}>Yoshi at work</Eyebrow>
        <div style={{ display: "flex", flexDirection: "column", gap: 9 }}>
          {handled.map((item) => <div key={item} style={{ display: "flex", gap: 9, alignItems: "flex-start" }}><span style={{ width: 5, height: 5, borderRadius: 999, flex: "none", marginTop: 7, background: "var(--accent-pos)" }} /><div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), lineHeight: 1.5, color: "var(--ink-2)" }}>{item}</div></div>)}
        </div>
      </section>}
      {marketContext && <section style={{ marginTop: 22, paddingTop: 18, borderTop: "1px solid var(--rule)" }}>
        <Eyebrow style={{ marginBottom: 8 }}>Your market context</Eyebrow>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), lineHeight: 1.52, color: "var(--ink-2)" }}>{marketContext.summary}</div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 6, marginTop: 12 }}>
          {marketContext.drivers.map(([label, detail]) => <div key={label} style={{ padding: "11px", borderRadius: 10, background: "var(--bg-2)", border: "1px solid var(--rule)" }}><div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 500 }}>{label}</div><div style={{ marginTop: 5, fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-3)", lineHeight: 1.45 }}>{detail}</div></div>)}
        </div>
      </section>}
    </div>
  );
};

const BriefDetail = ({ b: incomingBrief, onBack, onClose, nav, onAdvance, hasNext, onArchive, embedded, onExecute, onDecline, onAskProposal, onReviseProposal, onThreadJump, onApplyAutomationRevision }) => {
  const [b, setBrief] = useState(incomingBrief);
  const [msg, setMsg] = useState("");
  const [arch, toggleArchived, addArchived] = useArchivedBriefs();
  const isArchived = arch.includes(b.id);
  const isHoldingEvent = !!b.holdingEvent;
  const spaciousBrief = isHoldingEvent || b.portfolioUpdateDaily || b.earningsPre || b.earningsPost || b.investorProfile || b.automationMoveReview;
  const contentPadding = spaciousBrief ? "48px" : "18px 20px 20px";
  const footerX = spaciousBrief ? 48 : 20;
  // archived & resolved briefs are read-only — no chat field (kept conversation shows as history)
  const isClosed = isArchived || !!b.outcome;
  const queuesForOpen = !!b.proposal && briefQueuesForOpen(b);
  const showFooter = !b.outcome || !isClosed;
  const [thread, setThread] = useBriefThread(b.id, !b.automationMoveReview && !b.proposal?.automationRevision);
  const [typing, setTyping] = useState(false);
  const [revisionReview, setRevisionReview] = useState(null);
  const [automationDraft, setAutomationDraft] = useState(null);
  const sendingRef = useRef(false);
  const automationProposal = thread.find((m) => m.kind === "automation-proposal");
  const composerInput = useRef(null);
  const [draftError, setDraftError] = useState("");
  const prefillAutomationMessage = (intent, value = "____") => {
    setAutomationDraft(intent); setDraftError("");
    if (!intent) { setMsg(""); return; }
    const { choice, current } = intent;
    const amount = "$" + current.amount.toLocaleString("en-US", { maximumFractionDigits: 2 });
    const text = choice === "stop" ? "Pause this automation." : choice === "amount" ? `Change each ${current.ticker} purchase from ${amount} to $${value}.` : `Change this automation from ${window.AutomationReview.lowerFirst(window.AutomationReview.schedule(current).short)} to ${value}.`;
    setMsg(text);
    requestAnimationFrame(() => {
      const input = composerInput.current;
      if (!input) return;
      input.focus();
      const blank = text.indexOf("____");
      input.setSelectionRange(blank < 0 ? text.length : blank, blank < 0 ? text.length : blank + 4);
    });
  };
  const chooseAutomationDraft = (intent, value) => prefillAutomationMessage(intent, value);
  const sendAutomationDraft = (revision, text) => {
    if (!revision || automationProposal || sendingRef.current) return;
    acted.current = true;
    sendingRef.current = true;
    setAutomationDraft(null); setMsg(""); setDraftError("");
    const base = [...thread, { from: "user", t: text }];
    setThread(base); setTyping(true);
    setTimeout(() => {
      setTyping(false); sendingRef.current = false;
      setThread([...base, { from: "agent", t: "Here's the revised automation. Review it before anything changes." }, { ...revision, from: "agent", kind: "automation-proposal" }]);
    }, 700);
  };

  const scrollRef = useRef(null);
  const conversationRef = useRef(null);
  const proposalRef = useRef(null);
  // web: the approval stays only while the proposal is on screen (there's height
  // for both). Once the chat grows enough that the proposal scrolls out of view,
  // hide Decline / Confirm — you shouldn't approve what you can't see.
  const [proposalVisible, setProposalVisible] = useState(true);
  const lastThreadCount = useRef(thread.length);
  const acted = useRef(false);
  // after a revision, snap the pane back up to the now-updated proposal so you
  // can see the change and approve it (not left stranded at the bottom of chat)
  const pendingProposalScroll = useRef(false);
  const webInline = embedded && window.__YOSHI_WEB;
  const hasConversation = thread.length > 0 || typing;
  useEffect(() => { setBrief(incomingBrief); setMsg(""); setAutomationDraft(null); setRevisionReview(null); }, [incomingBrief.id]);
  useEffect(() => {
    if (!acted.current) { lastThreadCount.current = thread.length; return; }
    const el = scrollRef.current;
    if (!el) return;
    if (pendingProposalScroll.current) {
      pendingProposalScroll.current = false;
      requestAnimationFrame(() => el.scrollTo({ top: 0, behavior: "smooth" }));
      lastThreadCount.current = thread.length;
      return;
    }
    const firstWebMessage = webInline && lastThreadCount.current === 0 && thread.length > 0;
    requestAnimationFrame(() => {
      if (firstWebMessage && conversationRef.current) {
        el.scrollTo({ top: Math.max(0, conversationRef.current.offsetTop - 4), behavior: "smooth" });
      } else {
        el.scrollTo({ top: el.scrollHeight, behavior: webInline ? "smooth" : "auto" });
      }
    });
    lastThreadCount.current = thread.length;
  }, [thread, typing, webInline]);
  // watch the proposal against the reading-pane scroll: approval shows while any
  // of it is visible, hides once its bottom scrolls above the pane's top edge
  useEffect(() => {
    const root = scrollRef.current;
    if (!root) return;
    const check = () => {
      const el = proposalRef.current;
      if (!el) { setProposalVisible(true); return; }
      const r = el.getBoundingClientRect(), rr = root.getBoundingClientRect();
      setProposalVisible(r.bottom > rr.top + 8);
    };
    check();
    root.addEventListener("scroll", check, { passive: true });
    window.addEventListener("resize", check);
    return () => { root.removeEventListener("scroll", check); window.removeEventListener("resize", check); };
  }, [b.id, thread.length, typing]);
  const scrollToProposal = () => { const el = scrollRef.current; if (el) el.scrollTo({ top: 0, behavior: "smooth" }); };
  const send = () => {
    const t = msg.trim();
    if (!t || sendingRef.current) return;
    if (b.automationMoveReview && automationDraft) {
      const result = window.AutomationReview.fromMessage(b.automationMoveReview, automationDraft.current, t);
      if (result.error) { setDraftError(result.error); composerInput.current?.focus(); return; }
      if (result.revision) { sendAutomationDraft(result.revision, t); return; }
      setAutomationDraft(null); setDraftError("");
    }
    setMsg(""); acted.current = true;
    const first = thread.length === 0;
    const base = [...thread, { from: "user", t, time: "now" }];
    setThread(base);
    setTyping(true);
    setTimeout(() => {
      let revised = null;
      if (b.proposal && onReviseProposal && BRIEF_REVISION_RE.test(t)) {
        revised = onReviseProposal(b.id, t);
        if (revised) { setBrief(briefItemFromProposal(revised)); pendingProposalScroll.current = true; }
      } else if (b.proposal && onAskProposal && !onReviseProposal) {
        onAskProposal(t);
      }
      setTyping(false);
      setThread([...base, { from: "agent", t: b.proposal ? briefProposalReply(t, revised) : first && b.ask && b.ask[1] ? b.ask[1] : briefFollowup(t), time: "now" }]);
    }, 1100);
  };
  const attachDoc = (doc) => {
    acted.current = true;
    const base = [...thread, { from: "user", t: `Attached ${doc.name}`, time: "now" }];
    setThread(base);
    setTyping(true);
    setTimeout(() => { setTyping(false); setThread([...base, { from: "agent", t: attachReplyFor(doc), time: "now" }]); }, 1100);
  };
  const advance = onAdvance || onBack;
  const markRead = () => { addArchived(b.id); if (onArchive) onArchive(b.id); advance(); };
  const markUnread = () => { toggleArchived(b.id); if (onArchive) onArchive(b.id); advance(); };
  const retry = () => {
    if (!b.retry || !nav) return;
    if (openRetryDestination(b.retry, nav, onClose)) return;
    if (!nav.ask) return;
    onClose && onClose();
    nav.ask(b.retry.prompt, b.retry.reply);
  };
  const tag = b.outcome === "approved" ? ["Approved", "var(--accent-pos)"] :
  b.outcome === "declined" ? ["Declined", "var(--signal-neg)"] :
  isArchived ? ["Archived", "var(--ink-3)"] : null;
  if (revisionReview) {
    const proposal = { ...revisionReview, id: "p-automation-move-review-" + revisionReview.automationId, kind: "automation", net: 0, automationRevision: revisionReview };
    return <BriefDetail key={proposal.id} b={{ id: proposal.id, kind: "approve", icon: "bolt", title: proposal.title, body: proposal.why, legs: proposal.legs, proposal }} embedded={embedded} onBack={() => setRevisionReview(null)} nav={nav} onExecute={() => onApplyAutomationRevision?.(proposal)} onDecline={() => setRevisionReview(null)} />;
  }
  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 330, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Brief">
      {!embedded && window.StatusBar && <window.StatusBar />}
      {/* pushed sub-page: the back chevron is the only exit and returns to the
          briefs list. Closing the whole hub lives on the list's own nav bar. */}
      {!embedded &&
      <NavBar title="Briefs" onBack={onBack} />}
      {b.proposal?.automationRevision && <div style={{ padding: "12px 20px 0" }}><Btn kind="ghost" size="sm" onClick={onBack}>Back to brief</Btn></div>}
      <div className="scroll" ref={scrollRef} style={{ flex: 1, minHeight: 0, padding: contentPadding }}>
        <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
          <Icon name={b.icon || "bell"} size={14} color={b.kind === "insight" ? "var(--accent)" : "var(--ink-3)"} stroke={1.6} />
          {/* when a rule chip follows, the eyebrow stays bare — the chip
              carries the rule name once, as the tappable link */}
          <Eyebrow>{b.proposal?.briefLabel || (b.proposal ? "Needs you" : b.kind === "insight" ? "Insight" : BRIEF_TYPE[b.type] || "Brief")}{b.when && !b.source ? " · " + b.when : ""}</Eyebrow>
          {tag && <span style={{ marginLeft: "auto" }}><StatusTag label={tag[0]} color={tag[1]} /></span>}
        </div>
        {/* rule-fired brief — provenance chip, tappable through to the rule.
            The glyph stays Yoshi: your rules run inside Yoshi. */}
        {b.source && (
          <button className="press" onClick={() => nav && nav.automation && nav.automation(b.source.auto)} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 999, padding: "5px 10px 5px 8px", cursor: "pointer", marginTop: 10 }}>
            <Logo size={11} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", color: "var(--accent)" }}>{b.source.name}</span>
            <Icon name="back" size={11} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
          </button>
        )}
        <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-title)", fontWeight: 500, letterSpacing: "normal", marginTop: isHoldingEvent ? 18 : 9, lineHeight: 1.2 }}>{b.title}</div>
        {b.earningsPost?.market && <div style={{ display: "flex", alignItems: "baseline", flexWrap: "wrap", gap: "4px 8px", marginTop: 9, fontVariantNumeric: "proportional-nums" }}>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(15), fontWeight: 500, color: "var(--ink)" }}>{b.earningsPost.market.price}</span>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--accent-pos)" }}>{b.earningsPost.market.change}</span>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)" }}>{b.earningsPost.market.label}</span>
        </div>}
        {b.value != null &&
        <div style={{ marginTop: 10 }}>
            <Money value={b.value} size={20} weight={500} sign={b.tone === "in"} color={b.tone === "in" ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
          </div>
        }
        {b.value == null && b.stat && !b.outcome &&
        <div style={{ marginTop: 10, fontFamily: "var(--f-mono)", fontSize: typeSize(22), fontWeight: 500, fontVariantNumeric: "proportional-nums", color: b.tone === "pos" ? "var(--accent-pos)" : b.tone === "warn" ? "var(--signal-neg)" : "var(--ink)" }}>{b.stat}</div>
        }
        <div style={{ height: embedded ? 0 : 1, background: "var(--rule)", margin: embedded ? "16px 0 2px" : "16px 0" }} />
        {b.news ?
        <window.NewsBrief data={b.news} /> :
        <>
        {/* the highlights — 2–3 sentences: what happened (in dollars), why it
            matters to you, and what Yoshi did or can do next */}
        {!b.earningsPre && !b.earningsPost && !b.portfolioUpdateDaily && !b.holdingEvent && !b.debtOverview && !b.investorProfile && !b.automationMoveReview && <p style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", lineHeight: 1.6, color: "var(--ink)", margin: 0 }}>{b.body}</p>}
        {/* briefs without a detail section keep the canned answer inline */}
        {!b.detail && !b.reviewSections && !b.weeklySnapshot && !b.earningsPre && !b.earningsPost && !b.portfolioUpdateDaily && !b.holdingEvent && !b.investorProfile && b.ask && <p style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.6, color: "var(--ink-2)", margin: "12px 0 0" }}>{b.ask[1]}</p>}
        </>}
        {b.earningsPre && <EarningsPreWeb earnings={b.earningsPre} opening={b.body} />}
        {b.earningsPost && <EarningsPostWeb earnings={b.earningsPost} opening={b.body} onAsk={setMsg} />}
        {b.investorProfile && <InvestorProfileBriefWeb profile={b.investorProfile} opening={b.body} />}
        {b.payday && <PaydayBriefWeb payday={b.payday} onOpenAutomation={(id) => nav && nav.automation && nav.automation(id)} />}
        {b.debtOverview && <DebtOverviewBriefWeb overview={b.debtOverview} />}
        {b.portfolioUpdateDaily && <PortfolioUpdateDailyWeb update={b.portfolioUpdateDaily} />}
        {b.holdingEvent && <HoldingEventBrief event={b.holdingEvent} />}
        {b.automationMoveReview && <AutomationMoveReviewBrief key={b.id} review={b.automationMoveReview} onChoose={chooseAutomationDraft} locked={isClosed || !!automationProposal || typing} />}
        {b.visual && <InsightEvidence visual={b.visual} />}
        {/* the dig-in — line items behind the highlights, under the evidence */}
        {b.weeklySnapshot && <WeeklyReviewExplorer snapshot={b.weeklySnapshot} attention={b.attention} events={b.financialEvents} sectionLabel={b.weeklySectionLabel} handled={b.weeklyHandled} marketContext={b.marketContext} onAsk={(prompt) => setMsg(prompt)} />}
        {!b.weeklySnapshot && b.reviewSections && b.reviewSections.length > 0 && (
          <div style={{ marginTop: 20, display: "flex", flexDirection: "column", gap: 18 }}>
            {b.reviewSections.map((section) => (
              <section key={section.title}>
                <Eyebrow style={{ marginBottom: 7 }}>{section.title}</Eyebrow>
                <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), color: "var(--ink-2)", lineHeight: 1.55 }}>{section.body}</div>
              </section>
            ))}
          </div>
        )}
        {b.cadenceChoices && <ReviewCadenceChoice choices={b.cadenceChoices} />}
        {b.cadenceNote && <div style={{ marginTop: 22, paddingTop: 18, borderTop: "1px solid var(--rule)", fontFamily: "var(--f-display)", fontSize: typeSize(13), lineHeight: 1.5, color: "var(--ink)" }}>{b.cadenceNote}</div>}
        {b.detail && b.detail.length > 0 && (
          <div style={{ marginTop: 18 }}>
            <Eyebrow style={{ marginBottom: 9 }}>The detail</Eyebrow>
            <div style={{ display: "flex", flexDirection: "column", gap: 9 }}>
              {b.detail.map((d, i) => (
                <div key={i} style={{ display: "flex", gap: 9, alignItems: "flex-start" }}>
                  <span style={{ width: 5, height: 5, borderRadius: 999, flex: "none", background: "var(--accent)", marginTop: 7, opacity: 0.75 }} />
                  <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), color: "var(--ink-2)", lineHeight: 1.55 }}>{d}</span>
                </div>
              ))}
            </div>
          </div>
        )}
        {b.retry && !isClosed && (
          <div style={{ marginTop: 18, width: "min(184px, 100%)", border: "1px solid var(--accent)", background: "var(--bg-card)", borderRadius: 12, boxSizing: "border-box" }}>
            <div style={{ padding: "8px 10px 10px", display: "flex", flexDirection: "column" }}>
              <Eyebrow style={{ marginBottom: 8 }}>Next step</Eyebrow>
              <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 500, letterSpacing: "normal", lineHeight: 1.2 }}>Yoshi can reopen the flow</div>
              <div style={{ display: "flex", alignItems: "baseline", gap: 5, marginTop: 5 }}>
                <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-2)" }}>Failure context</span>
                <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(8.5), color: "var(--ink-3)", marginLeft: "auto" }}>attached</span>
              </div>
              <Btn full size="sm" onClick={retry} style={{ marginTop: 9 }}>{b.retry.cta}</Btn>
            </div>
          </div>
        )}
        {/* multi-transaction series — one card per transaction for review;
            the footer owns the single approval decision. */}
        {b.series &&
        <div style={{ marginTop: 18 }}>
          <Eyebrow style={{ marginBottom: 8 }}>Series</Eyebrow>
          <SeriesReviewRows items={b.series} />
        </div>
        }
        {b.legs && !b.series &&
        <div style={{ marginTop: 18 }}>
          <Eyebrow style={{ marginBottom: 8 }}>What we proposed</Eyebrow>
          <div className="ld-proposal-terms">
            {b.legs.map((leg, i) => {
              const [tag, label, detail, amount] = leg;
              const tagColor = tag === "SELL" ? "var(--signal-neg)" : tag === "BUY" ? "var(--accent-pos)" : "var(--ink-2)";
              return (
                <div key={i} style={{ display: "grid", gridTemplateColumns: PROPOSAL_LEG_COLUMNS, gap: 10, alignItems: "start", padding: "16px", borderBottom: i === b.legs.length - 1 ? "none" : "1px dashed var(--rule)" }}>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", fontWeight: 500, letterSpacing: "normal", color: tagColor }}>{tag}</span>
                  <ProposalLegContent label={label} detail={detail} amount={amount} />
                </div>);
            })}
          </div>
          {!b.proposal?.automationRevision && (b.proposal || b.amount) && <div ref={proposalRef}><ProposalAmountSummary p={b.proposal || b} /></div>}
          {/* first-run preview — the rule resolved against today's numbers */}
          {b.preview && (
            <div style={{ display: "flex", gap: 9, alignItems: "flex-start", marginTop: 10, padding: "11px 12px", borderRadius: 12, border: "1px solid color-mix(in srgb, var(--accent) 45%, transparent)", background: "color-mix(in srgb, var(--accent) 8%, transparent)" }}>
              <span style={{ marginTop: 1.5, flex: "none" }}><Icon name="clock" size={14} color="var(--accent)" stroke={1.7} /></span>
              <span style={{ minWidth: 0 }}>
                <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", fontWeight: 500 }}>{b.preview.t}</span>
                <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: "var(--ld-type-small)", color: "var(--ink-2)", marginTop: 3, lineHeight: 1.5 }}>{b.preview.s}</span>
              </span>
            </div>
          )}
        </div>
        }
        {queuesForOpen && <BriefClosedMarketStatus />}
        {/* in-brief conversation — persists, so returning to this brief shows
            the context. On web, it starts after the brief/proposal content and
            shares the same reading scroll surface. */}
        {hasConversation && webInline &&
        <div ref={conversationRef} style={{ display: "flex", alignItems: "center", gap: 12, margin: "24px 0 14px", paddingTop: 2 }}>
          <Eyebrow color="var(--accent)" style={{ whiteSpace: "nowrap" }}>Conversation</Eyebrow>
          <div style={{ height: 1, flex: 1, background: "var(--rule)" }} />
        </div>}
        {hasConversation &&
        <div style={{ marginTop: webInline ? 0 : 18, display: "flex", flexDirection: "column", gap: 9 }}>
          {hasConversation && !webInline && <Eyebrow color="var(--accent)" style={{ paddingTop: 2 }}>Conversation</Eyebrow>}
          {b.automationMoveReview && <Eyebrow style={{ marginBottom: 4 }}>Brief thread</Eyebrow>}
          {thread.map((m, i) => m.kind === "automation-proposal" ? <AutomationRevisionProposalTile key={i} proposal={m} onReview={setRevisionReview} onDiscard={() => setThread(thread.filter((item) => item !== m))} /> : <BriefBubble key={i} m={m} />)}
          {typing && <BriefTyping />}
        </div>}
      </div>
      {showFooter &&
      <div style={{ flex: "none", borderTop: "1px solid var(--rule)", paddingBottom: isClosed ? 22 : 0 }}>
        {/* catch-up actions — the outcome tag sits in the header; archived gets a revert */}
        {b.outcome ? null :
        b.proposal && onExecute ?
        (proposalVisible ?
        <div style={{ display: "flex", gap: 8, padding: `12px ${footerX}px 0` }}>
          <Btn kind="ghost" full onClick={() => onDecline && onDecline(b.id)}>Decline</Btn>
          {b.proposal?.automationRevision ? <Btn full onClick={() => onExecute && onExecute(b.id)}>Confirm with passkey</Btn> : <Btn full onClick={() => onExecute && onExecute(b.id)}>{queuesForOpen ? "Schedule for open" : "Confirm"}</Btn>}
        </div> :
        /* scrolled past the proposal — no approving what you can't see; offer a
           way back up to it instead */
        <div style={{ padding: `12px ${footerX}px 0` }}>
          <Btn kind="ghost" full onClick={scrollToProposal}><Icon name="down" size={15} style={{ transform: "rotate(180deg)" }} /> Back to the proposal to approve</Btn>
        </div>) :
        isArchived ?
        <div style={{ padding: `12px ${footerX}px 0` }}>
          <Btn full onClick={markUnread}>Mark as unread</Btn>
        </div> :
        <div style={{ display: "flex", gap: 8, padding: `12px ${footerX}px 0` }}>
          <Btn kind="ghost" full onClick={advance}>Keep unread</Btn>
          <Btn full onClick={markRead}>Mark as read</Btn>
        </div>}
        {/* chat field — live briefs only; archived/resolved are read-only.
            Web keeps needs-you proposal chat inline with the brief. */}
        {!isClosed &&
        <div style={{ margin: `12px ${footerX}px 22px` }}>
          <YoshiComposer inputRef={b.automationMoveReview ? composerInput : undefined} inputMode={automationDraft?.choice === "amount" ? "decimal" : undefined} value={msg} onChange={(text) => { setMsg(text); setDraftError(""); if (!text.trim()) setAutomationDraft(null); }} onSend={send} placeholder="Talk to Yoshi…" onAttach={attachDoc} onThreadJump={b.proposal && onThreadJump ? onThreadJump : null} />
          {b.automationMoveReview && draftError && <div role="alert" style={{ marginTop: 7, fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--signal-neg)" }}>{draftError}</div>}
        </div>}
      </div>}
    </div>);

};

/* a proposal teaser in the "Needs you" folder — opens the approval sheet */
const PropRow = ({ p, onReview, last }) =>
<button className="press" onClick={onReview} style={{
  width: "100%", textAlign: "left", background: "none", border: "none",
  borderBottom: last ? "none" : "1px solid var(--rule)", cursor: "pointer", padding: "13px 18px", display: "block"
}}>
    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
      <LiveDot />
      <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", color: "var(--ink-3)" }}>{p.agent}</span>
    </div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, marginTop: 4, letterSpacing: "normal" }}>{p.title}</div>
    <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 6, fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 500, color: "var(--accent)" }}>Review <Icon name="back" size={13} color="var(--accent)" style={{ transform: "scaleX(-1)" }} /></div>
  </button>;


/* the briefs body (collapsible sections + swipe-to-archive + new-brief CTA),
   reused by the full-screen BriefsHub and the Stream tab's Briefs view. */

/* Alerts are now strictly things that went wrong — failed moves, unfilled
   orders. Rare by design; an empty Alerts section is the healthy state. */
const ERROR_ALERTS = [
  { id: "err-acats-failed", title: "ACATS transfer failed", when: "9:36 AM", ts: 90936,
    body: "Schwab rejected the ACATS transfer from Schwab Brokerage ••6112 because the account registration does not match the Yoshi Brokerage account. Nothing moved into Yoshi, no positions were sold, and the pending transfer has been stopped. Confirm the account title or choose a different brokerage, then try ACATS again.",
    stat: "Failed", tone: "warn",
    detail: ["What happened: Schwab rejected the ACATS request before delivering assets.", "Why: the name and account type at Schwab did not match the receiving Yoshi Brokerage account.", "Next: confirm the legal name and account type, or link a different brokerage account, then start a new ACATS request."],
    retry: { cta: "Try move again", destination: { type: "transfer", intent: "deposit", from: "investment:schwab" }, prompt: "Help me retry the failed ACATS transfer.", reply: "The Schwab ACATS transfer failed because the account registration did not match the receiving Yoshi Brokerage account. Nothing moved into Yoshi. I can help you confirm the account title and account type, then start a new ACATS request." },
    ask: ["Why did my ACATS transfer fail?", "Schwab rejected the ACATS transfer before delivering assets because the account registration did not match the receiving Yoshi Brokerage account. Nothing moved into Yoshi and no positions were sold. Confirm the account title or choose another brokerage account, then I can help you try again."] },
  { id: "err-transfer-failed", title: "Transfer failed", when: "9:12 AM", ts: 90912,
    body: "Your $2,000.00 ACH transfer to Chase Checking ••4417 failed because Chase rejected the destination account details. No money left Yoshi Cash; the transfer was stopped before settlement and no fee was charged. Review the account number, routing number, and account name, then try the transfer again.",
    stat: "Failed", tone: "warn",
    detail: ["What happened: Chase rejected the ACH before settlement.", "Impact: No money left Yoshi Cash and the failed transfer was not posted.", "Next: verify the Chase account and routing details, then resend to the same account or pick a different destination."],
    retry: { cta: "Try move again", destination: { type: "transfer", intent: "withdraw", from: "cash" }, prompt: "Help me retry the failed Chase transfer.", reply: "The $2,000.00 ACH transfer to Chase failed because the destination account details were rejected. No money left Yoshi Cash. I can help you verify the account and routing details, then send it again or choose a different destination." },
    ask: ["Why did my Chase transfer fail?", "Chase rejected the ACH before settlement because the destination account details did not match. No money left Yoshi Cash and no fee was charged. Want me to help verify the details and try the transfer again?"] },
  { id: "err-automation-failed", title: "Automation failed", when: "8:41 AM", ts: 90841,
    body: "Weekly investing · VTI did not run this morning because the funding transfer it depends on is still pending. No order was placed, no shares were bought, and no cash moved. The automation is still active and will check again at the next market open; you can review the rule or run it once the cash lands.",
    stat: "Failed", tone: "warn",
    source: { name: "Weekly investing · VTI", auto: "a1" },
    detail: ["What happened: the automation reached market open, but the cash it was set to use was not available yet.", "Impact: No order was placed, no shares were bought, and no cash moved.", "Next run: Yoshi will check again at the next market open after the Chase transfer posts."],
    retry: { cta: "Review automation", destination: { type: "automation", id: "a1" }, prompt: "Help me review the failed Weekly investing automation.", reply: "Weekly investing · VTI did not run because the funding transfer it depends on is still pending. No order was placed and no cash moved. I can open the rule, confirm the funding dependency, or draft a one-time VTI buy once the cash lands." },
    ask: ["Why did my weekly VTI automation fail?", "Weekly investing · VTI reached market open before its funding transfer was available. No order was placed, no shares were bought, and no cash moved. The rule is still active and will check again at the next market open after the cash posts."] },
  { id: "err-trade-failed", title: "Trade failed", when: "7:58 AM", ts: 90758,
    body: "Your NVDA rebalance trade failed because the limit price was no longer available when the order reached the venue. No shares bought or sold, no cash moved, and your holdings stayed unchanged. I can rebuild the order at the current quote and ask you to approve it again.",
    stat: "Failed", tone: "warn",
    detail: ["What happened: NVDA moved past the approved limit before the venue accepted the order.", "Impact: No shares bought or sold, no cash moved, and the rebalance was cancelled.", "Next: reprice at the current quote and approve a fresh trade if you still want the rebalance."],
    retry: { cta: "Retry trade", destination: { type: "trade", id: "nvda", side: "BUY" }, prompt: "Help me retry the failed NVDA rebalance trade.", reply: "The NVDA rebalance failed because the approved limit price was no longer available when the order reached the venue. No shares bought or sold and no cash moved. I can rebuild the trade at the current quote for you to approve." },
    ask: ["Why did my rebalance fail?", "NVDA moved past your approved limit before the venue accepted the order. No shares bought or sold, no cash moved, and your holdings stayed unchanged. Want me to reprice the rebalance at the current quote and send a fresh approval?"] },
  { id: "err-partial", title: "Order partially filled", when: "Yesterday", ts: 81530,
    body: "Your “Buy the dip” automation filled 4.0 of 6.1 VOO shares at an average of $545.10 before liquidity ran out at your limit. The remaining 2.1 shares were cancelled at close — about $1,145.00 of the order stayed in Cash, unspent. Want the rest finished at the current price?",
    stat: "Partial", tone: "warn",
    ask: ["What happened with my VOO order?", "Your “Buy the dip” automation filled 4.0 of 6.1 VOO shares at an average of $545.10 before VOO ran out at your limit; the remaining 2.1 shares were cancelled at close and no further cash was used. Want me to finish the rest at the current price?"] },
];

/* total briefs shown in the hub — approvals + alerts + insights — so the bell
   badge reflects everything waiting, not just pending approvals. Mirrors the
   `ALL` feed built in BriefsBoard (archived items are excluded there too). */
const totalBriefCount = (proposalCount = 0) =>
  proposalCount
  + ERROR_ALERTS.length
  + (typeof BRIEF_INSIGHTS !== "undefined" ? BRIEF_INSIGHTS.length : 0)
  + (typeof BRIEFS !== "undefined" ? BRIEFS.filter((b) => b.folder === "insights" && (!b.automationMoveReview || window.AutomationReview.eligible(b.automationMoveReview).length > 0)).length : 0);
window.totalBriefCount = totalBriefCount;

/* examples of resolved proposals — kept in Archived for the record */
const APPROVED_EXAMPLE = {
  id: "apr-rebalance", kind: "alert", icon: "bolt", outcome: "approved",
  title: "Rebalance your investments",
  body: "You approved this rebalance on May 24 and it executed the same day: sold $2,400.00 of NVDA and $950.00 of AAPL, bought $3,350.00 of VOO — all inside the Roth IRA, so no tax impact.",
  stat: "Approved", tone: "pos", when: "May 24",
  ask: ["What did the rebalance do?", "On May 24 you approved selling $2,400.00 of NVDA and $950.00 of AAPL, then buying $3,350.00 of VOO in the same Roth IRA. It reduced concentrated single-stock exposure and brought the account closer to target."],
  legs: [["ACCOUNT", "Roth IRA \u2022\u20227731", "Current balance $314,815.05 \u00b7 Yoshi \u00b7 Retirement", ""], ["SELL", { ticker: "NVDA", price: "$142.17", change: "+$3.21 (2.31%) today", changeTone: "pos", name: "NVIDIA Corp", valueLabel: "Available $25,590.60" }, "", "-$2,400.00"], ["SELL", { ticker: "AAPL", price: "$214.05", change: "-$1.25 (0.58%) today", changeTone: "neg", name: "Apple Inc", valueLabel: "Available $31,037.25" }, "", "-$950.00"], ["BUY", { ticker: "VOO", price: "$548.12", change: "+$3.59 (0.66%) today", changeTone: "pos", name: "Vanguard S&P 500 ETF", valueLabel: "Current holding $32,887.20" }, "", "+$3,350.00"], ["TRADE DATE", "Today at market", "Market order after approval", ""]],
  amountLabel: "Trade amount", amount: "$6,700.00", cashImpactLabel: "Cash impact", cashImpact: "$0.00"
};
const REJECTED_EXAMPLE = {
  id: "rej-emergency-fund", kind: "alert", icon: "bolt", outcome: "declined",
  title: "Top up your emergency fund",
  body: "You declined this on May 26 — you wanted the $1,500.00 kept in Checking. Nothing moved, and the emergency-fund gap stays on my watch list.",
  stat: "Declined", tone: "warn", when: "May 26",
  ask: ["Why did I decline the emergency fund top-up?", "You declined moving $1,500.00 from Checking to Savings on May 26, noting you'd rather keep the cash in Checking. It's archived here for the record."],
  legs: [["FROM", "Checking \u2022\u20228841", "Current balance $18,420.55", "-$1,500.00"], ["TO", "Savings \u2022\u20222207", "Current balance $64,000.00", "+$1,500.00"], ["TIMING", "Instant transfer", "Arrives in seconds", "$0.00 fee"], ["REPEAT", "One-time transfer", "", ""]],
  amountLabel: "Transfer amount", amount: "$1,500.00"
};

/* one proposal → board-item mapping, shared by the board list and deep links
   (a "Waiting on you" tap must resolve the same item a row tap would) */
const briefItemFromProposal = (p) => ({ id: p.id, kind: "approve", icon: "bolt", agentIcon: p.agentIcon, series: p.series, title: p.title, body: p.briefBody || p.why.split(". ")[0] + ".", detail: p.detail, value: null, tone: p.net >= 0 ? "in" : "out", sec: "needsyou", when: p.when || "Today", ts: p.ts, agent: p.agent, source: p.source, preview: p.preview, legs: p.legs, wideLegs: p.wideLegs, net: p.net, amountLabel: p.amountLabel, amount: p.amount, cashImpactLabel: p.cashImpactLabel, cashImpact: p.cashImpact, proposal: p });

const BriefsBoard = ({ nav, onOpen, onClose = () => {}, proposals = [], onApprove, selectedId }) => {
  const [secs, setSecs] = useState({ needsyou: true, alerts: true, insights: true, archived: false });
  const [arch, archive] = useArchivedBriefs();
  const [q, setQ] = useState("");
  const toggle = (k) => setSecs((s) => ({ ...s, [k]: !s[k] }));

  // Needs you = approvals (mirrors Home). Alerts = errors only. Insights =
  // everything Yoshi surfaces for you (proactive reads, recaps, price alerts).
  const ALL = [
    ...proposals.map(briefItemFromProposal),
    ...ERROR_ALERTS.map((e) => ({ ...e, kind: "alert", icon: "bell", sec: "alerts" })),
    ...BRIEF_INSIGHTS.map((it) => ({ id: "ins-" + it.label, kind: "insight", icon: "bulb", title: it.label, body: it.read, ask: it.ask, stat: it.stat, tone: it.tone, visual: it.visual, source: it.source, detail: it.detail, when: it.when, ts: it.ts, sec: "insights" })),
    ...BRIEFS.filter((b) => b.folder === "insights" && (!b.automationMoveReview || window.AutomationReview.eligible(b.automationMoveReview).length > 0)).map((b) => ({ ...b, kind: "insight", icon: "bulb", sec: "insights" })),
  ];
  const ql = q.trim().toLowerCase();
  const matchQ = (x) => !ql || ((x.title || "") + " " + (x.body || "")).toLowerCase().includes(ql);
  // each section reads like an inbox: newest first, by the item's timestamp
  const inSec = (k) => (k === "archived" ? [APPROVED_EXAMPLE, REJECTED_EXAMPLE, ...ALL.filter((x) => arch.includes(x.id))] : ALL.filter((x) => x.sec === k && !arch.includes(x.id)).sort((a, b) => (b.ts || 0) - (a.ts || 0))).filter(matchQ);
  const SECTIONS = [["needsyou", "Needs you"], ["alerts", "Alerts"], ["insights", "Insights"], ["archived", "Archived"]];

  const emptyText = (k) =>
    k === "archived" ? "Nothing archived yet. Swipe a brief left to tuck it away here." :
    "You're all caught up.";

  const newBrief = () => {onClose();nav.ask("Set up a new brief.", "Sure, tell me what you'd like to keep an eye on. I can send a market recap on a schedule, ping you when a holding crosses a price, flag unusual spending, or summarize how an automation is running. So what should this brief watch, and how often?");};

  return (
    <>
      <div style={{ padding: "12px 18px 2px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 12px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 999 }}>
          <Icon name="search" size={16} color="var(--ink-3)" stroke={1.6} />
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search briefs"
            style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5) }} />
          {q && <button className="press" onClick={() => setQ("")} aria-label="Clear search" style={{ flex: "none", background: "none", border: "none", display: "flex", color: "var(--ink-3)", cursor: "pointer" }}><Icon name="close" size={15} /></button>}
        </div>
      </div>
      {SECTIONS.map(([k, label]) => {
        const items = inSec(k);
        const on = secs[k];
        return (
          <div key={k} style={{ paddingTop: 6 }}>
            <button className="press" onClick={() => toggle(k)} style={{ width: "100%", display: "flex", alignItems: "baseline", gap: 8, padding: "16px 18px 10px", background: "none", border: "none", cursor: "pointer", textAlign: "left" }}>
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(window.__YOSHI_WEB ? 17 : 14), fontWeight: 500, letterSpacing: "normal" }}>{label}</span>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), fontWeight: 500, color: "var(--accent)" }}>{items.length}</span>
              <Icon name="down" size={16} color="var(--ink-3)" style={{ marginLeft: "auto", transform: on ? "rotate(180deg)" : "none", transition: "transform 180ms ease" }} />
            </button>
            {on && (items.length ? (
              <div>
                {items.map((b, i) => (
                  <SwipeRow key={b.id} archived={k === "archived"} last={i === items.length - 1} onAction={() => archive(b.id)}>
                    <BriefRow b={b} last={i === items.length - 1} selected={b.id === selectedId} onOpen={k === "needsyou" && !window.__YOSHI_WEB ? (x) => onApprove && onApprove(x.id) : onOpen} />
                  </SwipeRow>
                ))}
              </div>
            ) : (
              <div style={{ padding: "0 18px 16px 30px", fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)", lineHeight: 1.5 }}>
                {emptyText(k)}
              </div>
            ))}
          </div>);
      })}

      <div style={{ height: 6 }} />
      <div style={{ padding: "18px 18px 0" }}>
        <button className="press" onClick={newBrief} style={{
          width: "100%", textAlign: "left", display: "flex", alignItems: "center", gap: 11, padding: "12px 14px",
          background: "var(--bg-card)", border: "1px dashed var(--rule-2)", borderRadius: 12, cursor: "pointer"
        }}>
          <span style={{ width: 30, height: 30, flex: "none", borderRadius: 999, border: "1px solid var(--rule-2)", display: "grid", placeItems: "center", color: "var(--ink-2)" }}>
            <Icon name="plus" size={16} stroke={1.7} />
          </span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 500 }}>Set up a new brief</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)", marginTop: 3 }}>Tell Yoshi what to watch: a recap, a price alert, a spend flag</div>
          </div>
          <Icon name="back" size={15} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
        </button>
      </div>
    </>);

};

const BriefsHub = ({ onClose, nav, proposals = [], onApprove, onExecute, onDecline, onAskProposal, initialBriefId, initialBrief }) => {
  const [open, setOpen] = useState(() => {
    if (initialBrief) return initialBrief;
    // a pending proposal (e.g. a rule's "Waiting on you" tap) opens its brief
    const p = initialBriefId && proposals.find((x) => x.id === initialBriefId);
    return p ? briefItemFromProposal(p) : null;
  });
  const feed = BRIEF_FEED();
  const idx = open ? feed.findIndex((x) => x.id === open.id) : -1;
  const hasNext = idx >= 0 && idx < feed.length - 1;
  const advance = () => { if (hasNext) setOpen(feed[idx + 1]); else setOpen(null); };
  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Briefs">
      {window.StatusBar && <window.StatusBar />}
      <NavBar title="Briefs" border={false}
      right={<button className="press" onClick={onClose} style={{ background: "none", border: "none", color: "var(--ink-3)", display: "flex" }}><Icon name="close" size={20} /></button>} />

      <div className="scroll" style={{ padding: "0 0 24px" }}>
        <BriefsBoard nav={nav} onOpen={setOpen} onClose={onClose} proposals={proposals} onApprove={onApprove} />
      </div>

      {open && <BriefDetail b={open} onBack={() => setOpen(null)} onClose={onClose} nav={nav} onAdvance={advance} hasNext={hasNext}
        onExecute={onExecute ? (id) => { setOpen(null); onExecute(id); } : undefined}
        onDecline={onDecline ? (id) => { setOpen(null); onDecline(id); } : undefined}
        onAskProposal={onAskProposal && open.proposal ? (text) => { setOpen(null); onAskProposal(open.id, text); } : undefined} />}
    </div>);

};

/* the bell sheet — recent brief alerts, with a tap-through to the full Stream */
const AlertsSheet = ({ onClose, nav, onSeeAll }) => {
  const [open, setOpen] = useState(null);
  const [cleared, setCleared] = useState([]);
  const alerts = ERROR_ALERTS.map((b) => ({ ...b, kind: "alert", icon: "bell" }));
  const shown = alerts.filter((b) => !cleared.includes(b.id));
  return (
    <>
      <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)", zIndex: 300, animation: "scrim-in 200ms ease both" }} />
      <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 301, background: "var(--bg)", borderTop: "1px solid var(--accent)", maxHeight: "82%", display: "flex", flexDirection: "column", animation: "sheet-in 320ms cubic-bezier(0.16,1,0.30,1) both" }}>
        <div style={{ display: "flex", justifyContent: "center", paddingTop: 8, flex: "none" }}><span style={{ width: 36, height: 4, background: "var(--rule-2)", borderRadius: 999 }} /></div>
        <div style={{ padding: "10px 20px 10px", display: "flex", alignItems: "baseline", gap: 9, flex: "none", borderBottom: "1px solid var(--rule)" }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(17), fontWeight: 500, letterSpacing: "normal" }}>Alerts</span>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(11.5), color: "var(--ink-3)" }}>{shown.length} new</span>
          <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
        </div>
        <div className="scroll" style={{ padding: "0 0 8px" }}>
          {shown.length ? shown.map((b, i) => (
            <SwipeRow key={b.id} last={i === shown.length - 1} label="Clear" icon="close" onAction={() => setCleared((c) => [...c, b.id])}>
              <BriefRow b={b} onOpen={setOpen} />
            </SwipeRow>
          )) : (
            <div style={{ padding: "40px 24px", textAlign: "center", fontFamily: "var(--f-display)", fontSize: typeSize(13), color: "var(--ink-3)" }}>You're all caught up.</div>
          )}
          <div style={{ padding: "14px 18px 18px" }}>
            <button className="press" onClick={() => { onClose(); onSeeAll && onSeeAll(); }} style={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: 6, padding: "11px 0", background: "none", border: "1px solid var(--rule-2)", borderRadius: 10, cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 500, color: "var(--ink-2)" }}>
              See all briefs in Stream <Icon name="back" size={14} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
            </button>
          </div>
        </div>
      </div>
      {open && <BriefDetail b={open} onBack={() => setOpen(null)} onClose={onClose} nav={nav} />}
    </>);

};

Object.assign(window, { BriefsHub, BriefsBoard, BriefDetail, AlertsSheet, BRIEF_FEED });
