/* data.jsx, Yoshi mobile · the whole dummy dataset + formatters.
   Numbers are plausible (net worth ≈ $397k). Tickers are real symbols,
   prices are illustrative. Everything here is static seed data; the app
   layers live state on top. Exported on `window`. */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// Static YOS-7601 presentation fixture; never infer a market session from the clock.
const performanceSession = new URLSearchParams(window.location.search).get("performanceSession");
const performanceMovementLabel = performanceSession === "pre-market" ? "pre-market"
  : "today";

// Illustrative per-share after-hours changes for the three expanded summaries.
const AFTER_HOURS_PRICE_CHANGE = { vti: 0.20, voo: 0.40, aapl: -0.20, nvda: 0.50, msft: 0.50 };
const afterHoursHoldingChange = (h) => (AFTER_HOURS_PRICE_CHANGE[h.id] || 0) * h.shares;
const splitSessionReturn = (abs, pct, afterAbs) => {
  const prior = pct ? abs / (pct / 100) : 0;
  const todayAbs = abs - afterAbs;
  const close = prior + todayAbs;
  return [
    { label: "today", abs: todayAbs, pct: prior ? todayAbs / prior * 100 : 0 },
    { label: "after hours", abs: afterAbs, pct: close ? afterAbs / close * 100 : 0 },
  ];
};

/* ---------- formatters ----------------------------------------------------- */
// Split a number into grouped whole + decimal so the cents can grey out.
const money = (n) => {
  const neg = n < 0;
  const [w, d] = Math.abs(n).toFixed(2).split(".");
  return { neg, whole: w.replace(/\B(?=(\d{3})+(?!\d))/g, ","), dec: d || "" };
};
// Full string e.g. "$1,234.56"
const usd = (n) => {
  const m = money(n);
  return (m.neg ? "-$" : "$") + m.whole + "." + m.dec;
};
const signed = (n) => { const m = money(n); return (n >= 0 ? "+" : "−") + "$" + m.whole + "." + m.dec; };
// Compact e.g. $397k, $1.2M, $840 — for tight labels.
const compactUsd = (n) => {
  const a = Math.abs(n), s = n < 0 ? "-" : "";
  if (a >= 1e6) return s + "$" + (a / 1e6).toFixed(2) + "M";
  if (a >= 1e3) return s + "$" + (a / 1e3).toFixed(2) + "k";
  return s + "$" + a.toFixed(2);
};
const pct = (n) => (n >= 0 ? "+" : "−") + Math.abs(n).toFixed(2) + "%";
/* combined performance readout — the canonical app-wide format: +$XX.XX (X.XX%) */
const perf = (abs, p) => signed(abs) + " (" + Math.abs(p).toFixed(2) + "%)";

/* ---------- seeded series generator ---------------------------------------
   mulberry32 PRNG → a deterministic walk from start→end so a holding's
   chart always matches its quoted % move. */
const mulberry32 = (a) => () => {
  a |= 0; a = (a + 0x6D2B79F5) | 0;
  let t = Math.imul(a ^ (a >>> 15), 1 | a);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
function series(seed, len, start, end, vol) {
  const rnd = mulberry32(seed);
  const pts = [];
  for (let i = 0; i < len; i++) {
    const t = i / (len - 1);
    const base = start + (end - start) * t;
    const noise = (rnd() - 0.5) * vol * (start || 1) * (1 - Math.abs(t - 0.5)); // taper at ends
    pts.push(i === len - 1 ? end : base + noise);
  }
  return pts;
}

/* ---------- the account model ---------------------------------------------- */
const HOLDINGS = [
  { id: "vti",  ticker: "VTI",  name: "Vanguard Total Market",  kind: "etf",    shares: 420,  avg: 235.10, last: 291.44, dch:  0.74, seed: 11,
    perf: { "1W": 1.2, "1M": 3.4, "1Y": 21.8 }, group: "investments",
    fund: [["Asset class","US Equity ETF"],["Expense ratio","0.03%"],["Yield","1.31%"],["52w range","224.10 – 296.80"]],
    news: [["Reuters","Broad market ETFs see record weekly inflows","2h"],["Bloomberg","Total-market funds outperform sector bets in Q1","Yest"]] },
  { id: "nvda", ticker: "NVDA", name: "NVIDIA Corp",            kind: "stock",  shares: 180,  avg: 88.40,  last: 142.17, dch:  2.31, seed: 22,
    perf: { "1W": 4.1, "1M": 9.7, "1Y": 64.2 }, group: "investments",
    fund: [["Market cap","$3.48T"],["P/E ratio","58.2"],["Day range","138.02 – 143.66"],["52w range","86.62 – 153.13"]],
    news: [["CNBC","NVIDIA extends rally on data-center demand","41m"],["WSJ","Chipmakers lead the tape into the close","3h"]] },
  { id: "aapl", ticker: "AAPL", name: "Apple Inc",              kind: "stock",  shares: 145,  avg: 171.20, last: 214.05, dch: -0.58, seed: 33,
    perf: { "1W": -0.9, "1M": 2.1, "1Y": 14.6 }, group: "investments",
    fund: [["Market cap","$3.27T"],["P/E ratio","33.4"],["Day range","212.88 – 216.40"],["52w range","164.08 – 237.49"]],
    news: [["The Verge","Apple's services revenue hits new high","1h"],["Bloomberg","Hardware guidance trimmed for the quarter","Yest"]] },
  { id: "voo",  ticker: "VOO",  name: "Vanguard S&P 500",       kind: "etf",    shares: 60,   avg: 402.30, last: 548.12, dch:  0.66, seed: 44,
    perf: { "1W": 1.0, "1M": 3.0, "1Y": 19.4 }, group: "investments",
    fund: [["Asset class","US Large-Cap ETF"],["Expense ratio","0.03%"],["Yield","1.27%"],["52w range","468.20 – 552.10"]],
    news: [["Morningstar","S&P 500 index funds extend gains","2h"]] },
  { id: "msft", ticker: "MSFT", name: "Microsoft Corp",         kind: "stock",  shares: 32,   avg: 332.50, last: 468.30, dch:  1.12, seed: 55,
    perf: { "1W": 2.2, "1M": 4.8, "1Y": 27.1 }, group: "investments",
    fund: [["Market cap","$3.48T"],["P/E ratio","36.9"],["Day range","462.10 – 470.55"],["52w range","362.90 – 471.00"]],
    news: [["Reuters","Microsoft cloud bookings beat estimates","4h"]] },

  { id: "btc",  ticker: "BTC",  name: "Bitcoin",                kind: "crypto", shares: 0.52, avg: 41200, last: 96180, dch:  3.42, seed: 66,
    perf: { "1W": 5.6, "1M": 12.4, "1Y": 118.0 }, group: "crypto",
    fund: [["Network","Bitcoin"],["Holdings","0.52000000 BTC"],["Cost basis","$41,200.00"],["24h range","92,400 – 97,010"]],
    news: [["CoinDesk","Bitcoin reclaims $96,000.00 as ETFs absorb supply","18m"],["Bloomberg","Spot BTC ETFs log eighth straight inflow day","2h"]] },
  { id: "eth",  ticker: "ETH",  name: "Ethereum",               kind: "crypto", shares: 4.2,  avg: 2210,  last: 3640, dch: -1.18, seed: 77,
    perf: { "1W": -2.1, "1M": 6.8, "1Y": 41.2 }, group: "crypto",
    fund: [["Network","Ethereum"],["Holdings","4.20000000 ETH"],["Cost basis","$2,210.00"],["24h range","3,580 – 3,712"]],
    news: [["The Block","Staking yields tick higher after upgrade","1h"]] },
  { id: "sol",  ticker: "SOL",  name: "Solana",                 kind: "crypto", shares: 120,  avg: 96.10, last: 188.40, dch:  4.77, seed: 88,
    perf: { "1W": 8.2, "1M": 19.3, "1Y": 96.5 }, group: "crypto",
    fund: [["Network","Solana"],["Holdings","120.000 SOL"],["Cost basis","$96.10"],["24h range","176.20 – 191.00"]],
    news: [["Decrypt","Solana network activity sets monthly record","3h"]] },
];

// Derived per-holding numbers.
HOLDINGS.forEach(h => {
  h.value   = h.shares * h.last;
  h.cost    = h.shares * h.avg;
  h.pl      = h.value - h.cost;
  h.plPct   = (h.pl / h.cost) * 100;
  h.dayAbs  = h.value * (h.dch / 100);

});

const CASH = [
  { id: "chk",  name: "Cash",     sub: "Yoshi ••8841", apr: "3.30% APY", value: 18420.55, color: "var(--ink-3)" },
  { id: "sav",  name: "Reserve",  sub: "Yoshi ••2207", apr: "3.30% APY", value: 64000.00, color: "var(--accent-2)" },
];

const GROUPS = {
  cash:        { label: "Cash",        items: CASH,                                       color: "var(--ink-2)" },
  investments: { label: "Investments", items: HOLDINGS.filter(h => h.group === "investments"), color: "var(--accent-4)" },
  crypto:      { label: "Crypto",      items: HOLDINGS.filter(h => h.group === "crypto"),      color: "var(--accent)" },
};

const sum = (arr) => arr.reduce((s, x) => s + x.value, 0);
const CASH_TOTAL   = sum(CASH);
const INVEST_TOTAL = sum(GROUPS.investments.items);
const CRYPTO_TOTAL = sum(GROUPS.crypto.items);
const NET_WORTH    = CASH_TOTAL + INVEST_TOTAL;

/* Liabilities, read-only, linked from outside Yoshi via Plaid (like the
   external accounts in the Accounts tab). Net worth nets these against assets. */
const LIABILITIES = [
  { id: "mortgage", name: "Home mortgage", sub: "Wells Fargo ••7704", value: 238400.00, color: "var(--signal-neg)", min: "$2,140.00/mo · 27 yr left", apr: "5.88% APR", connectionStatus: "reconnect",
    debt: { outstanding: "$238,400.00", original: "$310,000.00", term: "30 yr fixed", payoff: "Mar 2052", paid: "$146,200.00", nextMin: "$2,140.00 · Jun 1" } },
  { id: "sapphire", name: "Chase Sapphire", sub: "Credit card ••6841", value: 3210.44, color: "var(--signal-neg)", min: "$35.00 min · due Jun 14", apr: "21.99% APR", connectionStatus: "linkActivity",
    debt: { outstanding: "$3,210.44", original: "$12,000.00 limit", term: "Revolving", payoff: "Revolving", paid: "$8,940.00", nextMin: "$35.00 · Jun 14" } },
  { id: "amex",     name: "Amex Gold",      sub: "Credit card ••2008", value: 1180.62, color: "var(--signal-neg)", min: "$40.00 min · due Jun 9", apr: "24.24% APR", connectionStatus: "activate",
    debt: { outstanding: "$1,180.62", original: "$10,000.00 limit", term: "Revolving", payoff: "Revolving", paid: "$5,210.00", nextMin: "$40.00 · Jun 9" } },
];
const LIAB_TOTAL   = sum(LIABILITIES);
const ASSET_TOTAL  = NET_WORTH;                 // alias, assets held at Yoshi
const NET_WORTH_TRUE = ASSET_TOTAL - LIAB_TOTAL; // assets minus liabilities
const DAY_ABS      = GROUPS.investments.items.reduce((s, h) => s + h.dayAbs, 0); // cash doesn't move
const DAY_PCT      = (DAY_ABS / (NET_WORTH - DAY_ABS)) * 100;

/* Net-worth intraday series → ends at NET_WORTH, with two automation markers. */
const NW_SERIES = series(101, 34, NET_WORTH - DAY_ABS - 1800, NET_WORTH, 0.0016);
const NW_MARKERS = [];

/* ---------- account registry · powers the Activity account filter ----------
   Accounts money sits in or moves through, grouped so the filter can offer the
   right options for the active scope chip (Yoshi / External / Card). `masks`
   are the ··NNNN that appear in a row's detail/acct strings. */
const LEDGER_ACCOUNTS = [
  { id: "y-cash",    group: "yoshi",    name: "Cash",             label: "··8841",     masks: ["8841"] },
  { id: "y-reserve", group: "yoshi",    name: "Reserve",          label: "··2207",     masks: ["2207"] },
  { id: "y-broker",  group: "yoshi",    name: "Brokerage",        label: "Individual", masks: ["2208", "7731"] },
  { id: "y-crypto",  group: "yoshi",    name: "Crypto",           label: "Spot",       masks: [] },
  { id: "chase",     group: "external", name: "Chase Checking",   label: "··3392",     masks: ["3392", "4471"] },
  { id: "schwab",    group: "external", name: "Schwab Brokerage", label: "··5520",     masks: ["5520"] },
  { id: "card-4417", group: "card",     name: "Debit card",       label: "··4417",     masks: ["4417"] },
  { id: "sapphire",  group: "card",     name: "Chase Sapphire",   label: "··6841",     masks: ["6841"] },
];

/* the account(s) a row touches — masks first, then a scope+category fallback
   for rows that name no account. Works for ledger txns (accountScope) and queue
   items (scope). A transfer can touch two accounts, hence an array. */
const accountIdsOf = (row) => {
  const hay = [row.title, row.detail, row.acct].filter(Boolean).join(" ");
  const hit = LEDGER_ACCOUNTS.filter((a) => a.masks.some((m) => hay.includes(m))).map((a) => a.id);
  if (hit.length) return hit;
  const scope = row.accountScope || row.scope;
  const cat = row.category || "";
  if (scope === "yoshi") {
    if (cat === "Investment" || cat === "Trade") return ["y-broker"];
    if (cat === "Reserve") return ["y-reserve"];
    return ["y-cash"];
  }
  if (scope === "external") return ["chase"];
  if (scope === "card") return ["card-4417"];
  return [];
};

/* ---------- agent proposals (inbox) ---------------------------------------- */
const PROPOSALS = [
  /* BNK_CHECKING_BUFFER — sweep excess at month-end after approval, retaining
     1.5 times average ordinary monthly outflows over 90 complete days.
     A low checking balance prompts a notification, never an automatic refill. */
  { id: "p-checking-buffer", briefKey: "BNK_CHECKING_BUFFER", agent: "Yoshi agent", channel: "in-app", when: "Today", ts: 100400, title: "You could be earning $14.39 more per month on your cash",
    briefLabel: "Insight",
    summary: "I can move extra cash to Yoshi each month while keeping $9,000 in checking.",
    summaryFacts: { action: "sweep_excess_cash", checkingTarget: 9000, averageMonthlyOutflows: 6000, lookbackCompleteDays: 90, targetMultiplier: 1.5, sourceAccount: "Chase Total Checking", destinationAccount: "Yoshi", sweepTiming: "month-end", lowBalanceResponse: "notify" },
    why: "I can move extra money from your checking account to Yoshi at the end of each month, where it can earn 3.30% APY. You’ve spent an average of $6,000 a month over the last 90 days, so I’d leave $9,000 in checking to cover a month and a half of your usual expenses.",
    briefBody: "I can move extra money from your checking account to Yoshi at the end of each month, where it can earn 3.30% APY. You’ve spent an average of $6,000 a month over the last 90 days, so I’d leave $9,000 in checking to cover a month and a half of your usual expenses.",
    net: 0.00, settles: "At month-end", settlesVerb: "starts", kind: "automation", cat: "cash", amountLabel: "Checking target", amount: "$9,000.00", wideLegs: true,
    legs: [["WHEN", "At month-end", "Checks after recent activity settles", ""], ["IF", "Checking has more than $9,000.00", "Enough for 1.5 months of your usual spending", ""], ["DO", "Move the extra cash to Yoshi", "From Chase Total Checking", ""], ["LIMITS", "Keep at least $9,000.00 in checking", "Based on your last 90 days of spending", ""], ["MODE", "Runs automatically", "After approval; pause or edit anytime", ""]],
    preview: { t: "This month: $5,231.42 could move to Yoshi", s: "I’ll let you know if your checking balance looks like it needs a top-up." } },
  /* BNK_CARD_SPEND_SPIKE — a one-time early-payment proposal after posted
     week-to-date card spend materially exceeds the comparable baseline.
     Future spike monitoring remains a separate permission. */
  { id: "p-card-spend-spike", briefKey: "BNK_CARD_SPEND_SPIKE", agent: "Yoshi agent", channel: "in-app", when: "Today", ts: 100300, title: "Your Chase Sapphire spending is up 38% this week",
    why: "You've spent $2,480.00 on Chase Sapphire so far this week—$680.00 more than your $1,800.00 eight-week average by Wednesday. A $620.00 hotel stay and $410.00 flight made up most of the increase. Want me to pay the $3,140.00 posted balance early from Yoshi Cash?",
    briefBody: "You've spent $2,480.00 on Chase Sapphire so far this week—$680.00 more than your $1,800.00 eight-week average by Wednesday. A $620.00 hotel stay and $410.00 flight made up most of the increase. Want me to pay the $3,140.00 posted balance early from Yoshi Cash?",
    net: -3140.00, settles: "Usually 1–2 business days", settlesVerb: "settles", kind: "transfer", cat: "debt", amountLabel: "Payment amount", amount: "$3,140.00",
    legs: [["FROM", "Yoshi Cash ••8841", "Available balance $12,400.00 · $9,260.00 after payment", "−$3,140.00"], ["TO", "Chase Sapphire ••6841", "Posted balance $3,140.00", "$3,140.00"]] },
  /* BNK_CARD_LIMIT_BUFFER — a posted balance at 90% or more of the card limit
     creates one exact payment proposal. Future monitoring is offered only
     after this payment and remains a separate permission. */
  { id: "p-card-limit-buffer", briefKey: "BNK_CARD_LIMIT_BUFFER", agent: "Yoshi agent", channel: "in-app", when: "Today", ts: 100200, title: "Your Chase Sapphire is at 93% of its limit",
    why: "You have $700.00 of available credit left on your Chase Sapphire. Paying $1,300.00 would bring the balance down to 80% of its limit and restore $2,000.00 of available credit. Want me to make the payment from Yoshi Cash?",
    briefBody: "You have $700.00 of available credit left on your Chase Sapphire. Paying $1,300.00 would bring the balance down to 80% of its limit and restore $2,000.00 of available credit. Want me to make the payment from Yoshi Cash?",
    net: -1300.00, settles: "Usually 1–2 business days", settlesVerb: "settles", kind: "transfer", cat: "debt", amountLabel: "Payment amount", amount: "$1,300.00",
    legs: [["FROM", "Yoshi Cash ••8841", "Available balance $12,400.00", ""], ["TO", "Chase Sapphire ••6841", "Balance $9,300.00", ""]] },
  /* BNK_WEEKLY_CARD_PAY — a one-card, Yoshi-Cash-funded Friday payment
     automation. The setup defaults to asking before every weekly payment. */
  { id: "p-weekly-card-pay", briefKey: "BNK_WEEKLY_CARD_PAY", agent: "Yoshi agent", channel: "in-app", when: "Today", ts: 100100, title: "Set up weekly payments for Chase Sapphire",
    why: "You pay Chase Sapphire in full about once a month. Paying more often keeps more credit available when Chase checks your balance, and spreads out the cash hit instead of leaving one large bill. Want me to set this up?",
    briefBody: "You pay Chase Sapphire in full about once a month. Paying more often keeps more credit available when Chase checks your balance, and spreads out the cash hit instead of leaving one large bill. Want me to set this up?",
    net: 0.00, settles: "Every Friday", settlesVerb: "starts", kind: "automation", cat: "debt", amountLabel: "Payment plan", amount: "Full posted balance",
    legs: [["WHEN", "Every Friday, starting this Friday", "", ""], ["DO", "Pay the full posted balance from Yoshi Cash to Chase Sapphire", "", ""], ["MODE", "I'll ask before each payment, or you can make them automatic", "", ""]],
    preview: { t: "First payment: $3,200.00 this Friday", s: "If the balance is unchanged, Yoshi Cash would have $9,200.00 available after payment." } },
  /* BNK_CARD_INTEREST_PAYDOWN — a posted interest charge plus enough available
     payment funding creates a one-time Needs You brief. The proposed payment
     and any later autopay enrollment remain separate permissions. */
  { id: "p-interest-paydown", briefKey: "BNK_CARD_INTEREST_PAYDOWN", agent: "Yoshi agent", channel: "in-app", when: "Today", ts: 100000, title: "You were charged $84.00 in interest",
    why: "I noticed you were charged interest on your Chase Sapphire. You have enough cash to cover the balance. Want me to pay it off now so you don't keep paying interest you don't need to?",
    briefBody: "I noticed you were charged interest on your Chase Sapphire. You have enough cash to cover the balance. Want me to pay it off now so you don't keep paying interest you don't need to?",
    net: -3200.00, settles: "Usually 1–2 business days", settlesVerb: "settles", kind: "transfer", cat: "debt", amountLabel: "Payment amount", amount: "$3,200.00",
    legs: [["FROM", "−$3,200.00 from Yoshi Cash ••8841", "Available balance $12,400.00", ""], ["TO", "Chase Sapphire ••6841", "Current card balance $3,200.00", ""]] },
  /* setup brief authored from the chat prompt "Buy $500 NVDA daily at market
     open" — honors the instruction but flags the concentration (NVDA already
     18% vs the 10% band) and builds in a pause-at-25% guardrail. */
  { id: "p-nvda-daily-dca", agent: "Yoshi agent", channel: "in-app", when: "9:43 AM", ts: 90943, title: "Buy $500.00 of NVDA every market day",
    why: "This buys $500.00 of NVDA at the open every market day — about $2,500.00 a week, roughly $10,500.00 a month. It's dollar-cost averaging, so you pay the average price over time instead of trying to time it. Worth knowing before you approve: NVDA is already 18% of your investments, above your 10% target band, and buying daily deepens that — so I've added a guardrail that pauses the rule if NVDA tops 25%.",
    net: -500.00, settles: "Starts tomorrow", settlesVerb: "starts", kind: "automation", cat: "investments", amountLabel: "Daily amount", amount: "$500.00",
    legs: [["WHEN", "Every market day at 9:30 AM ET", "Skips market holidays", ""], ["DO", "Buy $500.00 of NVDA at market", "Yoshi Brokerage ••4410 · market order at the open", "$500.00"], ["LIMITS", "Exactly $500.00 per run", "Skips any day Cash is below $15,000.00, or if NVDA tops 25% of your portfolio", ""], ["MODE", "Runs automatically", "Within the limits above — switch to ask-me-each-time anytime", ""]],
    preview: { t: "First run: tomorrow, 9:30 AM ET — about 3.52 shares", s: "At today's $142.17, $500.00 buys about 3.52 shares of NVDA. This lifts NVDA from 18% toward your 25% guardrail over the coming weeks." } },
  /* a FIRING brief — one run of an ask-each-time rule. Same one-time proposal
     card as everything else; `source` carries the rule provenance (chip in the
     card + review header, deep-links to the automation detail). */
  { id: "p-buffer-fire", agent: "Yoshi agent", channel: "in-app", when: "9:02 AM", ts: 90902, title: "Move $6,240.00 to Reserve",
    source: { auto: "a2", name: "Keep a cash buffer", approvals: 4, limits: "max $12,000.00 a week, never below your $15,000.00 buffer" },
    why: "Your buffer rule fired this morning: a deposit settled and spendable Cash is $21,240.55 — above the $15,000.00 you keep on hand. Yoshi will move the $6,240.00 excess into Reserve, where it earns the same 3.30% APY. You chose to approve each run.",
    net: 0.00, settles: "In seconds", kind: "transfer", cat: "cash", amountLabel: "Transfer amount", amount: "$6,240.00",
    legs: [["WHY NOW", "Cash crossed your $15,000.00 line", "Checked at 9:02 AM, after your deposit settled", ""], ["FROM", "Yoshi Cash ••8841", "Current balance $21,240.55", "-$6,240.00"], ["TO", "Yoshi Reserve ••2207", "Current balance $64,000.00", "+$6,240.00"], ["TIMING", "Instant transfer", "Arrives in seconds", "$0.00 fee"], ["APPROVAL", "This run only", "The rule keeps asking each time", ""]] },
  /* a MULTI-TRANSACTION brief — one series, each transaction shown for review.
     The brief footer owns the single confirm-or-decline decision. */
  { id: "p-refund-split", agent: "Yoshi agent", channel: "in-app", when: "8:15 AM", ts: 90815, title: "Put your $4,800.00 refund to work",
    why: "Your IRS refund landed in Chase yesterday. Yoshi proposes deploying it in four moves — pull it in, pay down the card, shore up Reserve, and invest the rest. Review the full series, then confirm or decline it once; nothing runs until you do.",
    net: 4800.00, settles: "In seconds", settlesVerb: "settles", kind: "transfer", cat: "cash", amountLabel: "Series total", amount: "$4,800.00",
    series: [
      { tag: "PULL", title: "Pull the refund into Yoshi", sub: "Chase Total Checking ••3392 → Yoshi Cash ••8841", amt: "+$4,800.00" },
      { tag: "PAY", title: "Pay Chase Sapphire", sub: "Statement drops to $2,010.44 · zero interest", amt: "−$1,200.00" },
      { tag: "MOVE", title: "Top up Reserve", sub: "Yoshi Cash → Yoshi Reserve ••2207", amt: "$1,600.00" },
      { tag: "BUY", title: "Buy VOO", sub: "Market order · Yoshi Brokerage ••4410", amt: "$2,000.00" },
    ],
    legs: [["FROM", "Chase Total Checking ••3392", "IRS refund · settled yesterday", "+$4,800.00"], ["TO", "Yoshi Cash ••8841", "Then split across card, Reserve, and VOO", ""]] },
  { id: "p-transfer-once", agent: "Yoshi agent", channel: "in-app", when: "Yesterday", ts: 81200, title: "Move $2,000.00 to Reserve",
    why: "Yoshi will make a one-time instant transfer of $2,000.00 from Chase Total Checking ••3392 to your Yoshi Reserve ••2207. This will not repeat.",
    net: 0.00, settles: "In seconds", kind: "transfer", cat: "cash", amountLabel: "Transfer amount", amount: "$2,000.00",
    legs: [["FROM", "Chase Total Checking ••3392", "Current balance $14,231.42", "-$2,000.00"], ["TO", "Yoshi Reserve ••2207", "Current balance $64,000.00", "+$2,000.00"], ["TIMING", "Instant transfer", "Arrives in seconds", "$0.00 fee"], ["REPEAT", "One-time transfer", "", ""]] },
  { id: "p-trade-once", agent: "Yoshi agent", channel: "in-app", when: "Yesterday", ts: 81100, title: "Buy $1,400.00 of VTI at the market",
    why: "Yoshi will place a one-time market buy for $1,400.00 of VTI in your Roth IRA using the live price. Current live price is $291.44, up $2.16 (0.74%) today. Final shares will depend on the execution price.",
    net: -1400.00, settles: "today at market", settlesVerb: "trades", kind: "trade", cat: "investments", amountLabel: "Trade amount", amount: "$1,400.00", cashImpactLabel: "Cash impact", cashImpact: "-$1,400.00", timingLabel: "Trade timing", timing: "Today at market",
    legs: [["BUY", { ticker: "VTI", price: "$291.44", change: "+$2.16 (0.74%) today", changeTone: "pos", name: "Vanguard Total Market", valueLabel: "Order amount $1,400.00" }, "", "$1,400.00"], ["FROM", "Roth IRA cash ••7731", "Current balance $4,291.28", "-$1,400.00"], ["TRADE DATE", "Today at market", "Market order after approval", ""]] },
  /* setup briefs use the SAME contract slots the automation detail pins
     (WHEN / IF / DO / LIMITS / MODE) — the vocabulary is learned once, at
     approval. `preview` resolves the rule against today's numbers: what the
     first run would do right now, or why it wouldn't fire yet. */
  { id: "p0", agent: "Yoshi agent", channel: "in-app", when: "Jun 2", ts: 70900, title: "Set up recurring deposit",
    why: "Deposit $500.00 from Chase Total Checking ••3392 into Yoshi Cash ••8841 on the first business day of every month.",
    net: 500.00, settles: "Jun 2", settlesVerb: "starts", kind: "automation", cat: "cash", amountLabel: "Transfer amount", amount: "$500.00",
    legs: [["WHEN", "Monthly · 1st business day", "Starting Mon Jun 2", ""], ["DO", "Deposit $500.00 into Yoshi Cash", "Chase Total Checking ••3392 → Yoshi Cash ••8841", "$500.00"], ["LIMITS", "Exactly $500.00 per run", "Skips if Chase would drop below $1,000.00", ""], ["MODE", "Runs automatically", "No approval per run — pause or edit anytime", ""]],
    preview: { t: "First run: Mon Jun 2 — moves $500.00", s: "Chase is at $14,231.42 today, well clear of the $1,000.00 floor, so the run would go through." } },
  { id: "p-cash-threshold-dca", agent: "Yoshi agent", channel: "in-app", when: "Jun 2", ts: 70930, title: "DCA cash above $50,000.00",
    why: "When Yoshi Cash rises above $50,000.00 and the excess is at least $1,000.00, Yoshi will invest only the excess across AAPL, GOOG, and TSLA. Each buy uses live prices when the automation runs.",
    net: 0.00, settles: "When excess is available", settlesVerb: "starts", kind: "automation", cat: "investments", amountLabel: "Minimum trigger", amount: "$1,000.00 excess", cashImpactLabel: "Cash impact", cashImpact: "$0.00",
    legs: [["WHEN", "Yoshi Cash rises above $50,000.00", "Checked after every deposit settles", ""], ["IF", "The excess is at least $1,000.00", "Smaller excess waits for the next check", ""], ["DO", "Invest the excess in thirds", "Market buys of AAPL, GOOG, and TSLA at live prices", "33.33% each"], ["LIMITS", "Only dollars above $50,000.00", "Cash never drops below the line", ""], ["MODE", "Asks you each time", "One brief per round of buys — switch to automatic anytime", ""]],
    preview: { t: "Wouldn't fire today", s: "Yoshi Cash is $21,240.55 — $28,759.45 below your $50,000.00 line. Yoshi starts checking after every deposit once you approve." } },
  { id: "p-spy-tax-efficient-sell-down", agent: "Yoshi agent", channel: "in-app", when: "Jun 1", ts: 60900, title: "Sell down SPY tax efficiently",
    why: "Over the next 6 months, Yoshi will evenly reduce your SPY position and prioritize long-term lots with the least gains first. Each sell uses the live market price when its monthly run starts.",
    net: 0.00, settles: "Monthly", settlesVerb: "starts", kind: "automation", cat: "investments", amountLabel: "Automation amount", amount: "$18,000.00", cashImpactLabel: "Cash impact", cashImpact: "$0.00",
    legs: [["WHEN", "Monthly · for 6 months", "Evenly sized runs from Brokerage ••2208", ""], ["DO", "Sell about $3,000.00 of SPY", "Long-term, lowest-gain lots first · live market price", ""], ["LIMITS", "Long-term lots only", "Never touches short-term gains · stops after 6 runs", ""], ["MODE", "Asks you each time", "One brief per monthly sell — switch to automatic anytime", ""]],
    preview: { t: "First sell: Mon Jun 2 — about $3,000.00 of SPY", s: "Starts with your longest-held, lowest-gain lot at the live market price." } },
  { id: "p1", agent: "Yoshi agent", channel: "in-app", when: "9:25 AM", ts: 90925, title: "Rebalance your investments",
    why: "Your Roth IRA equity sleeve is above target after NVDA and AAPL outperformed. Yoshi would rotate $3,350.00 from concentrated single-stock exposure into VOO: sell $2,400.00 of NVDA and $950.00 of AAPL, then buy $3,350.00 of VOO in the same Roth IRA. Orders use live market prices checked again before approval.",
    net: 1153.66, settles: "today at market", settlesVerb: "trades", kind: "trade", cat: "investments", amountLabel: "Trade amount", amount: "$6,700.00", cashImpactLabel: "Cash impact", cashImpact: "$0.00", timingLabel: "Trade timing", timing: "Today at market",
    legs: [["ACCOUNT", "Roth IRA ••7731", "Current balance $314,815.05 · Yoshi · 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", ""]] },
  /* an external agent's proposal — identity is the agent's own brand mark
     (ChatGPT / Claude / …), never the Yoshi glyph */
  { id: "p2", agent: "ChatGPT", agentIcon: "chatgpt", channel: "whatsapp", when: "8:12 AM", ts: 90812, title: "Pay Chase Sapphire",
    why: "Your $2,340.18 statement is due in 6 days at 21.99% APR. Paying it in full from Cash now means you owe $0.00 interest and keep the card at a zero balance, and if you miss it the interest starts piling up on the whole balance.",
    net: -2340.18, settles: "Tomorrow", settlesVerb: "closes", kind: "transfer", cat: "debt", amountLabel: "Transfer amount", amount: "$2,340.18",
    legs: [["PAY","Chase Sapphire ••6841","Current balance $3,210.44 · 21.99% APR","$2,340.18"],["FROM","Cash ••8841","Current balance $82,420.55","-$2,340.18"]] },
];

const COMPLETED = [
  { id: "c1", title: "Sweep idle cash", detail: "$12,000.00 · Cash to Reserve", when: "May 29", net: 0, by: "Yoshi agent" },
  { id: "c2", title: "Bought VOO", detail: "8.234 sh @ $548.12", when: "May 28", net: -4513.20, by: "Weekly investing · VTI" },
  { id: "c3", title: "Sold BTC", detail: "0.0421 @ $96,180.00", when: "May 25", net: 4049.18, by: "You" },
  { id: "c4", title: "Paid IRS · Q1 estimated", detail: "$8,432.00 · Cash", when: "Apr 22", net: -8432.00, by: "Yoshi agent" },
];

/* ---------- the stream · all activity, grouped by day -----------------------
   A mix of executions, deposits, a weekly summary digest, and a custom report
   the user asked Yoshi for. Newly-approved proposals get prepended to Today. */
const ACTIVITY = [
  { day: "Today", items: [
    { type: "report", kind: "Weekly summary", title: "Your week with Yoshi", detail: "Net worth +$3,212.00 · 4 automations ran · 1 proposal approved · $237.00 interest earned", when: "9:00" },
    { type: "exec", title: "Sweep idle cash", detail: "Cash to Reserve", when: "11:08", net: 0, amount: 12000, by: "Yoshi agent" },
    { type: "exec", title: "Bought VTI", detail: "2.10 sh @ $291.44 · auto-invest", when: "9:31", net: -612.00, by: "Weekly investing · VTI" },
  ] },
  { day: "Yesterday", items: [
    { type: "exec", title: "Bought VOO", detail: "8.234 sh @ $548.12", when: "15:42", net: -4513.20, by: "Weekly investing · VTI" },
    { type: "report", kind: "Custom report", title: "Dining spend · last 90 days", detail: "$1,840.00 across 38 charges. Trending −12.00% vs prior 90d", when: "14:10" },
    { type: "deposit", title: "Pay day", detail: "Acme Corp · direct deposit", when: "08:02", net: 6240.00 },
  ] },
  { day: "Earlier this week", items: [
    { type: "exec", title: "Sold BTC", detail: "0.0421 @ $96,180.00", when: "Mon", net: 4049.18, by: "You" },
    { type: "exec", title: "Reserved tax", detail: "5.00% of deposit to Tax 2026", when: "Sun", net: -1747.20, by: "Yoshi agent" },
    { type: "exec", title: "Paid Chase card", detail: "Statement balance · Cash", when: "Sun", net: -2340.18, by: "Yoshi agent" },
  ] },
  { day: "Apr 22", items: [
    { type: "exec", title: "Paid IRS · Q1 estimated", detail: "$8,432.00 · Cash", when: "Apr 22", net: -8432.00, by: "Yoshi agent" },
  ] },
];

/* ---------- chat seed ------------------------------------------------------ */
const CHAT_SEED = [
  { from: "agent", t: "Morning. Your accounts are up $4,802.00 today, mostly NVDA and SOL. One thing needs you: your Roth IRA has grown past the mix you wanted, so I'd like to even things out.", time: "9:24" },
  { from: "user",  t: "what would that do", time: "9:25" },
  { from: "agent", t: "Three notional orders inside your Roth IRA: sell $2,400.00 of NVDA, sell $950.00 of AAPL, and buy $3,350.00 of VOO. You'd reduce concentrated single-stock exposure and keep the account near target. Here it is, approve whenever you're ready.", time: "9:25", proposal: "p1" },
  { from: "user", t: "set up a recurring deposit into yoshi from my external cash account", time: "9:28" },
  { from: "agent", t: "I drafted the setup brief. Review and approve it to get started or ask me to change any parameter first.", time: "9:28", proposal: "p0" },
];

const QUICK_REPLIES = ["Set up monthly deposit", "Recurring research report", "How's my cash doing?", "Anything need my approval?"];

/* ---------- chat threads · past conversations with Yoshi -------------------
   The live thread is held in component state (seeded from CHAT_SEED above).
   These are read-only history, newest first. */
const THREADS = [
  { id: "th_tbill", title: "Earning more on idle cash", when: "May 26", preview: "Moved $15,000.00 of idle cash into short-term Treasuries at about 5.10%.",
    msgs: [
      { from: "user",  t: "what should i do with the idle cash in savings", time: "2:14 PM" },
      { from: "agent", t: "$15,000.00 has sat unused for 9 days. I'd move it into short-term Treasuries earning about 5.10%, your money stays available every month. Here's the proposal.", time: "2:14 PM" },
      { from: "user",  t: "looks good", time: "2:15 PM" },
      { from: "agent", t: "It's ready, approve whenever you like.", time: "2:15 PM" },
    ] },
  { id: "th_dca", title: "Pause investing for vacation", when: "May 18", preview: "Paused the weekly VTI buy. Kept the cash sweep running.",
    msgs: [
      { from: "user",  t: "pause my investing while i'm away next week", time: "7:40 AM" },
      { from: "agent", t: "Paused the weekly VTI buy. Nothing runs until you turn it back on. I'll keep moving idle cash to your Reserve unless you'd like that off too.", time: "7:41 AM" },
      { from: "user",  t: "keep the sweep on", time: "7:41 AM" },
      { from: "agent", t: "Will do. Enjoy the trip.", time: "7:41 AM" },
    ] },
  { id: "th_tax", title: "Tax reserve setup", when: "May 2", preview: "New rule: reserve 28.00% of every freelance deposit.",
    msgs: [
      { from: "user",  t: "i keep underpaying my quarterly taxes", time: "11:02 AM" },
      { from: "agent", t: "I can fix that at the source. New rule: reserve 28.00% of every freelance deposit into a Tax 2026 bucket, automatically. You'd never spend it by accident.", time: "11:02 AM" },
      { from: "user",  t: "do it", time: "11:03 AM" },
      { from: "agent", t: "Live. The first reserve runs on your next deposit. I'll log each one to your stream.", time: "11:03 AM" },
    ] },
  { id: "th_nvda", title: "Why NVDA dropped", when: "Apr 28", preview: "Sector pullback, not company-specific. Held on.",
    msgs: [
      { from: "user",  t: "why is nvda down today", time: "3:30 PM" },
      { from: "agent", t: "Sector-wide pullback after a downgrade across chipmakers, not company-specific. You're still up 64.00% on it, so this is just noise and nothing I'd act on.", time: "3:30 PM" },
      { from: "user",  t: "ok, leave it", time: "3:31 PM" },
    ] },
];

/* a Transfer always reads with a direction — "Transfer in" for money arriving,
   "Transfer out" for money leaving. `signed` is the row's net (or signed amount);
   net-zero internal moves (e.g. to Reserve) read as "in" toward the named side. */
const directionalCategory = (category, signed) => category === "Transfer" ? ((signed || 0) < 0 ? "Transfer out" : signed > 0 ? "Transfer in" : "Transfer") : category;

const transactionOccurrenceMs = (row) => {
  const value = row && row.occurredAt;
  if (typeof value === "number") return value;
  const parsed = Date.parse(value || "");
  return Number.isFinite(parsed) ? parsed : 0;
};
const sortTransactionsNewestFirst = (rows) =>
  [...rows].sort((left, right) => transactionOccurrenceMs(right) - transactionOccurrenceMs(left));
const transactionStamp = (row) => {
  if (row.when === "Just now") return "Just now";
  const date = row.when === "Today" ? "" : row.when;
  return [date, row.time].filter(Boolean).join(" · ") || row.when || "";
};
const currentTransactionOccurrence = () => {
  const now = new Date();
  return {
    occurredAt: now.toISOString(),
    occurredOn: "Today",
    time: now.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }),
  };
};

/* ---------- recent activity · a real transaction ledger -------------------
   More than agent moves: card spend, payroll, transfers, dividends, refunds. */
const TRANSACTIONS = [
  { id: "t1", icon: "shopping-bag", title: "Whole Foods Market", detail: "Card ··4417", when: "Today", time: "2:41 PM", occurredAt: "2026-05-29T14:41:00-07:00", net: -142.38, category: "Spending", accountScope: "external" },
  { id: "t2", icon: "banknote", title: "Northwind Labs", detail: "Direct deposit", when: "Today", time: "8:02 AM", occurredAt: "2026-05-29T08:02:00-07:00", net: 8200.00, category: "Income", accountScope: "external", acct: "Direct deposit" },
  { id: "t3", icon: "circle-plus", title: "Bought VTI", detail: "2.10 sh @ $291.44", when: "May 28", time: "3:47 PM", occurredAt: "2026-05-28T15:47:00-07:00", net: -612.00, category: "Investment", accountScope: "yoshi", cat: "automations", autoId: "a1" },
  { id: "t4", icon: "arrow-left-right", title: "Reserve", detail: "Yoshi ··8841 to Reserve ··2207", when: "May 28", time: "2:18 PM", occurredAt: "2026-05-28T14:18:00-07:00", net: 0, amount: 6000.00, type: "Internal transfer", rail: "Instant transfer", category: "Transfer", accountScope: "yoshi", cat: "automations", autoId: "a2" },
  { id: "t5", icon: "arrow-up-right", title: "Maya Cohen", detail: "Zelle ··4471", when: "May 28", time: "10:06 AM", occurredAt: "2026-05-28T10:06:00-07:00", net: -250.00, type: "Sent to a person", rail: "Zelle", category: "Transfer", accountScope: "external", acct: "Zelle ··4471" },
  { id: "t6", icon: "refresh-cw", title: "Dividend · VTI", detail: "Reinvested", when: "May 27", time: "11:32 AM", occurredAt: "2026-05-27T11:32:00-07:00", net: 84.20, category: "Investment", accountScope: "yoshi" },
  { id: "t7", icon: "shopping-bag", title: "Uber", detail: "Card ··4417", when: "May 26", time: "9:18 PM", occurredAt: "2026-05-26T21:18:00-07:00", net: -23.90, category: "Travel", accountScope: "external" },
  { id: "t8", icon: "landmark", title: "Interest paid", detail: "3.30% APY", when: "May 26", time: "12:01 AM", occurredAt: "2026-05-26T00:01:00-07:00", net: 237.00, category: "Reserve", accountScope: "yoshi" },
  { id: "t9", icon: "refresh-cw", title: "Refund · Delta Air Lines", detail: "Card ··4417", when: "May 25", time: "4:12 PM", occurredAt: "2026-05-25T16:12:00-07:00", net: 312.40, category: "Travel", accountScope: "external" },
  { id: "t10", icon: "credit-card", title: "Netflix", detail: "Subscription · Card ··4417", when: "May 25", time: "8:00 AM", occurredAt: "2026-05-25T08:00:00-07:00", net: -15.49, category: "Subscriptions", accountScope: "external" },
  { id: "t11", icon: "circle-plus", title: "Bought AAPL", detail: "Round-ups · 0.066 sh", when: "May 25", time: "3:56 PM", occurredAt: "2026-05-25T15:56:00-07:00", net: -14.20, category: "Investment", accountScope: "yoshi", cat: "automations", autoId: "a4" },
  { id: "t12", icon: "circle-minus", title: "Sold AAPL", detail: "18.9 sh @ $214.05 · You", when: "May 24", time: "1:45 PM", occurredAt: "2026-05-24T13:45:00-07:00", net: 4049.18, category: "Investment", accountScope: "yoshi" },
  { id: "t13", icon: "landmark", title: "Con Edison", detail: "Autopay · Card ··4417", when: "May 23", time: "4:30 PM", occurredAt: "2026-05-23T16:30:00-07:00", net: -118.04, category: "Bills", accountScope: "external" },
  { id: "t14", icon: "credit-card", title: "Chase Sapphire", detail: "Checking to Credit card ··6841", when: "May 23", time: "9:12 AM", occurredAt: "2026-05-23T09:12:00-07:00", net: -2340.18, type: "Card payment", rail: "ACH", category: "Transfer", accountScope: "yoshi", cat: "automations", autoId: "a5" },
  { id: "t15", icon: "refresh-cw", title: "Dividend · AAPL", detail: "Reinvested", when: "May 22", time: "10:08 AM", occurredAt: "2026-05-22T10:08:00-07:00", net: 6.40, category: "Investment", accountScope: "yoshi" },
  { id: "t16", icon: "arrow-up-right", title: "Chase Checking", detail: "Chase to Yoshi ··3392", when: "May 22", time: "3:20 PM", occurredAt: "2026-05-22T15:20:00-07:00", net: -2000.00, type: "External transfer", rail: "Standard ACH", category: "Transfer", accountScope: "external", acct: "Chase ··3392" },
];

/* ---------- automations · standing rules the agents run --------------------
   mode: "auto" (executes within limits) | "asks" (a brief per run) | "alert"
   (data only, never moves money). `rule` = the approved contract, rendered
   with the same slots at setup, on the detail, and (past tense) per run.
   `runs` = outcome-vocabulary history (ran/approved/declined/skipped/failed);
   `upNext` = armed/pending states, incl. waiting on arriving funds.

   LIST-ROW GRAMMAR — every slot is machine-derivable, no hand-written copy:
   · cadence  = the TRIGGER as a short temporal clause, from the bounded
                per-trigger-type template catalog ("Every Friday · market
                open", "After every deposit", "When a statement closes").
   · now.t    = the STATE machine: "Next · <date>" | "Waiting on you" |
                "Watching" | "On hold" | "Paused".
   · now.s    = the LAST OUTCOME: "<outcome-verb> <date> [· $amt]" using the
                run-history verb vocabulary — except On hold, which shows
                the fix ("reconnect Chase"). */
const AUTOMATIONS = [
  /* runTx: the template that turns an executed run row into the SAME receipt
     Activity opens — tap a run, get the transaction detail. sign: -1 money
     out, 0 internal move (net-zero to Yoshi, like Activity's). */
  { id: "a1", name: "Weekly investing · VTI", status: "active", cadence: "Every Friday · market open", measure: "+$184.00", sub: "this month", mode: "auto",
    runTx: { icon: "trade", category: "Investment", accountScope: "yoshi", detail: "Auto-invest · market open", sign: -1 },
    now: { t: "Next · Fri", s: "ran May 23 · $500.00" },
    summary: "Buy $500.00 of VTI every Friday at market open.", next: "Next run · Fri May 30",
    rule: [["WHEN", "Every Friday at market open", "9:30 AM ET", ""], ["DO", "Buy $500.00 of VTI", "Yoshi Brokerage ••4410 · market order", ""], ["LIMITS", "Max $500.00 per run", "Skips if Cash would drop below $500.00", ""], ["MODE", "Runs automatically", "Within the limits above — no approval needed", ""]],
    upNext: [{ k: "pending", t: "Next buy is armed for Friday's market open", s: "Yoshi will buy $500.00 of VTI if Cash is still above the $500.00 floor." }, { k: "watching", t: "Watching the cash floor before the open", s: "If Cash drops below the limit, the run skips and the history shows why." }],
    record: "11 runs · $5,500.00 total",
    runs: [{ d: "May 30", k: "ran", t: "Bought $500.00 VTI at open", amt: "$500.00" }, { d: "May 23", k: "ran", t: "Bought $500.00 VTI at open", amt: "$500.00" }, { d: "May 16", k: "ran", t: "Bought $500.00 VTI at open", amt: "$500.00" }, { d: "May 9", k: "ran", t: "Bought $500.00 VTI at open", amt: "$500.00" }, { d: "May 2", k: "ran", t: "Bought $500.00 VTI at open", amt: "$500.00" }, { d: "Apr 25", k: "skipped", t: "Buy skipped — cash floor", s: "Cash was $312.40, below your $500.00 floor. Yoshi didn't buy.", amt: "" }] },
  { id: "a2", name: "Keep a cash buffer", status: "active", cadence: "After every deposit", measure: "+$237.00", sub: "interest, 90d", mode: "asks",
    runTx: { icon: "swap", category: "Transfer", type: "Internal transfer", accountScope: "yoshi", detail: "Cash ••8841 → Reserve ••2207", sign: 0 },
    now: { t: "Waiting on you", s: "asked 9:02 AM", tone: "accent", briefId: "p-buffer-fire" },
    nowAfter: { t: "Watching", s: "moved $6,240.00 today" },
    summary: "Keeps your spendable Cash near $15,000.00 and moves the excess into your Reserve. Both earn 3.30% APY.", next: "Re-checks after every deposit",
    rule: [["WHEN", "A deposit settles in Yoshi Cash", "Also checked once each morning", ""], ["IF", "Spendable Cash is above $15,000.00", "Only the excess is ever moved", ""], ["DO", "Move the excess to Reserve", "Yoshi Cash ••8841 → Yoshi Reserve ••2207", ""], ["LIMITS", "Max $12,000.00 per week", "Never leaves Cash below $15,000.00", ""], ["MODE", "Asks you each run", "One brief per move — switch to automatic anytime", ""]],
    upNext: [{ k: "pending", t: "Asked you this morning — move $6,240.00 to Reserve", s: "Cash is $21,240.55 after a deposit settled. The brief is waiting in Needs you.", briefId: "p-buffer-fire" }, { k: "watching", t: "Watching for your next deposit — expected Tue", s: "Payroll usually lands Tuesdays. Yoshi re-checks the buffer after it settles." }],
    record: "19 runs · $83,000.00 total",
    runs: [{ d: "May 28", k: "approved", t: "Moved excess to Reserve", s: "Asked first — you approved in about 2 hours", amt: "$6,000.00" }, { d: "May 21", k: "skipped", t: "Nothing above the buffer", s: "Cash was $14,890.12 — below your $15,000.00 line", amt: "" }, { d: "May 14", k: "approved", t: "Moved excess to Reserve", s: "Asked first — you approved in 20 minutes", amt: "$8,000.00" }, { d: "May 7", k: "declined", t: "You kept the excess in Cash", s: "You said a tuition bill was coming, so Yoshi left it", amt: "" }, { d: "Apr 30", k: "approved", t: "Moved excess to Reserve", s: "Asked first — you approved overnight", amt: "$12,000.00" }, { d: "Apr 22", k: "skipped", t: "Weekly cap already reached", s: "The rule's $12,000.00-per-week limit stopped this run", amt: "" }, { d: "Apr 16", k: "approved", t: "Moved excess to Reserve", s: "Asked first — you approved in an hour", amt: "$9,000.00" }, { d: "Apr 9", k: "expired", t: "Ask expired unanswered", s: "The brief waited 3 days, then a fresh ask replaced it after the next morning's check", amt: "" }] },
  { id: "a3", name: "Lower taxes on gains", status: "watching", cadence: "Checks daily", measure: "+$420.00", sub: "saved ytd", mode: "asks",
    now: { t: "Watching", s: "asked May 27" },
    summary: "Watches for investments that have dropped, so selling one can reduce the taxes you owe on your gains.", next: "No action needed today",
    rule: [["WHEN", "Every trading day after close", "Checks taxable holdings and realized gains", ""], ["IF", "A tax-loss harvest looks useful", "Loss is meaningful and wash-sale risk is clear", ""], ["DO", "Ask before placing harvest trades", "Yoshi prepares the lot and replacement plan", ""], ["LIMITS", "No trade without approval", "Never sells a position only for tax impact", ""], ["MODE", "Asks you each run", "One brief per harvest opportunity", ""]],
    upNext: [{ k: "watching", t: "Next scan runs after today's close", s: "Yoshi is checking taxable lots against realized gains and wash-sale windows." }, { k: "pending", t: "May 27 AMD harvest ask expired", s: "No sale was placed; Yoshi will refresh the opportunity if it is still useful." }],
    record: "3 asks · $420.00 saved YTD",
    runs: [{ d: "May 27", k: "expired", t: "AMD harvest ask expired", s: "The lot was down $180.00, but you did not approve before the window closed.", amt: "$180.00 loss" }, { d: "May 12", k: "declined", t: "You skipped the SOL harvest", s: "Yoshi flagged a $240.00 loss; you kept the position unchanged.", amt: "$240.00 loss" }, { d: "Apr 18", k: "approved", t: "Harvested loss against gains", s: "Asked first — you approved the trade plan and Yoshi logged the tax impact.", amt: "$420.00 saved" }] },
  { id: "a4", name: "Round-ups into AAPL", status: "active", cadence: "After every card swipe", measure: "+$62.00", sub: "invested, 30d", mode: "auto",
    runTx: { icon: "trade", category: "Investment", accountScope: "yoshi", detail: "Round-ups · weekly buy", sign: -1 },
    now: { t: "Next · Sun", s: "ran Jun 1 · $14.20" },
    summary: "Rounds each card purchase to the nearest dollar and buys AAPL weekly.", next: "Next buy · Sun Jun 1",
    rule: [["WHEN", "After every Yoshi card swipe", "Round-up balance settles through the week", ""], ["DO", "Buy AAPL with weekly round-ups", "Yoshi Brokerage ••4410 · market order", ""], ["LIMITS", "Max $25.00 per weekly buy", "Skips if the round-up pool is under $5.00", ""], ["MODE", "Runs automatically", "Within the limits above — no approval needed", ""]],
    upNext: [{ k: "pending", t: "Next buy is scheduled for Sunday", s: "$8.40 in round-ups is ready so far; final amount locks before the order queues." }, { k: "watching", t: "Collecting round-ups from new card swipes", s: "Each swipe adds the spare change to this week's AAPL buy." }],
    record: "31 runs · $214.00 total",
    runs: [{ d: "Jun 1", k: "ran", t: "Bought AAPL from round-ups", s: "0.066 shares at $214.05", amt: "$14.20" }, { d: "May 25", k: "ran", t: "Bought AAPL from round-ups", s: "Weekly pool settled before the order queued.", amt: "$11.80" }, { d: "May 18", k: "ran", t: "Bought AAPL from round-ups", amt: "$13.40" }, { d: "May 11", k: "ran", t: "Bought AAPL from round-ups", amt: "$9.60" }, { d: "May 4", k: "skipped", t: "Round-up pool below minimum", s: "Only $3.80 had accumulated, so Yoshi held it for the next run.", amt: "" }] },
  /* "needs attention" — a rule with a broken data dependency goes loud, says
     exactly what it can't see and what's on hold, and carries the fix inline. */
  { id: "a5", name: "Pay card in full", status: "attention", cadence: "When a statement closes", measure: "$0.00", sub: "interest paid", mode: "auto",
    runTx: { icon: "swap", category: "Transfer", type: "Card payment", rail: "ACH", accountScope: "yoshi", detail: "Cash → Chase Sapphire ••6841", sign: -1 },
    now: { t: "On hold", s: "reconnect Chase", tone: "neg" },
    summary: "Pays the full statement balance from Cash the day before it's due.", next: "On hold · reconnect Chase Sapphire to resume",
    attention: { t: "Chase Sapphire disconnected", s: "Yoshi can't see the statement balance, so the Jun 2 payment is on hold. Nothing is paid until the connection is back.", cta: "Reconnect Chase Sapphire" },
    rule: [["WHEN", "Statement closes", "Chase Sapphire ••6841, day before due", ""], ["DO", "Pay the full statement balance", "Yoshi Cash ••8841 → Chase Sapphire", ""], ["LIMITS", "Never below minimum payment", "Skips if Cash would drop below $2,000.00", ""], ["MODE", "Runs automatically", "Within the limits above — no approval needed", ""]],
    upNext: [{ k: "pending", t: "Jun 2 payment is on hold", s: "Yoshi cannot read the statement balance until Chase Sapphire is reconnected." }, { k: "watching", t: "Next run resumes after reconnect", s: "Once the connection is back, Yoshi re-checks the due date and Cash floor before paying." }],
    record: "3 runs · $7,160.00 total",
    runs: [{ d: "Jun 2", k: "failed", t: "Payment on hold — Chase Sapphire disconnected", s: "Yoshi couldn't read the statement balance. Reconnect to resume; nothing was paid.", amt: "" }, { d: "May 2", k: "ran", t: "Paid Chase Sapphire in full", amt: "$2,480.00" }, { d: "Apr 2", k: "ran", t: "Paid Chase Sapphire in full", amt: "$1,920.00" }, { d: "Mar 2", k: "ran", t: "Paid Chase Sapphire in full", amt: "$2,760.00" }] },
  { id: "a6", name: "Monthly spending report", status: "active", cadence: "Monthly · the 1st", measure: "May", sub: "last sent", mode: "alert",
    now: { t: "Next · Jun 1", s: "sent May 1" },
    summary: "Compiles a categorized breakdown of your spending across every account and card, then delivers it to your inbox on the first of the month. No money moves.", next: "Next report · Jun 1",
    rule: [["WHEN", "On the 1st of each month", "After the prior month closes", ""], ["DO", "Send a spending report", "Categories, subscriptions, and month-over-month changes", ""], ["LIMITS", "Read-only alert", "No money moves and no trades are placed", ""], ["MODE", "Alerts only", "Yoshi sends the brief; you decide what to do next", ""]],
    upNext: [{ k: "pending", t: "June report sends on Jun 1", s: "It will summarize May spending across Yoshi and connected accounts." }, { k: "watching", t: "Tracking category changes through month-end", s: "Dining, travel, and subscriptions are being compared against April." }],
    record: "5 reports sent",
    runs: [{ d: "Jun 1", k: "ran", t: "Spending report delivered", s: "12 categories, with dining and travel called out.", amt: "12 categories" }, { d: "May 1", k: "ran", t: "Spending report delivered", s: "April spending was compared against March.", amt: "12 categories" }, { d: "Apr 1", k: "ran", t: "Spending report delivered", amt: "11 categories" }, { d: "Mar 1", k: "skipped", t: "Report skipped — account sync still settling", s: "Two card feeds were still catching up, so Yoshi waited for complete data.", amt: "" }] },
  /* a PAUSED rule — nothing runs, the row says so, and the detail's primary
     action flips to Resume. History stays. */
  { id: "a7", name: "Weekend savings top-up", status: "paused", cadence: "Every Sunday", measure: "+$1,400.00", sub: "moved, lifetime", mode: "auto",
    runTx: { icon: "swap", category: "Transfer", type: "Internal transfer", accountScope: "yoshi", detail: "Cash ••8841 → Reserve ••2207", sign: 0 },
    now: { t: "Paused", s: "paused by you · May 18" },
    summary: "Moves $200.00 from Cash into Reserve every Sunday evening.", next: "Paused · nothing runs until you resume it",
    rule: [["WHEN", "Every Sunday · 6:00 PM", "", ""], ["DO", "Move $200.00 to Reserve", "Yoshi Cash ••8841 → Yoshi Reserve ••2207", ""], ["LIMITS", "Exactly $200.00 per run", "Skips if Cash would drop below $2,000.00", ""], ["MODE", "Runs automatically", "Within the limits above — no approval needed", ""]],
    upNext: [{ k: "watching", t: "Paused — no upcoming transfer is armed", s: "Resume the rule to schedule the next Sunday top-up." }, { k: "pending", t: "Next eligible run would be Sunday at 6:00 PM", s: "Yoshi will re-check Cash and the $2,000.00 floor before moving money." }],
    record: "7 runs · $1,400.00 total",
    runs: [{ d: "May 18", k: "ran", t: "Moved $200.00 to Reserve", s: "Last run before you paused it", amt: "$200.00" }, { d: "May 11", k: "ran", t: "Moved $200.00 to Reserve", amt: "$200.00" }, { d: "May 4", k: "ran", t: "Moved $200.00 to Reserve", amt: "$200.00" }, { d: "Apr 27", k: "skipped", t: "Cash floor protected", s: "Cash was $1,940.20 — below your $2,000.00 floor", amt: "" }] },
  { id: "a8", name: "Payday reserve transfer", status: "active", cadence: "When payroll lands", measure: "$500.00", sub: "moved today", mode: "auto",
    runTx: { icon: "swap", category: "Transfer", type: "Internal transfer", accountScope: "yoshi", detail: "Yoshi Cash ••8841 → Yoshi Reserve ••2207", sign: 0 },
    now: { t: "Completed", s: "ran today · $500.00" },
    summary: "Moves $500.00 from Yoshi Cash to Reserve after a confirmed paycheck lands.", next: "Next run · when payroll lands",
    rule: [["WHEN", "A paycheck lands in Yoshi Cash", "Confirmed posted payroll only", ""], ["DO", "Move $500.00 to Reserve", "Yoshi Cash ••8841 → Yoshi Reserve ••2207", ""], ["LIMITS", "Exactly $500.00 per paycheck", "Skips if Cash would fall below $2,000.00", ""], ["MODE", "Runs automatically", "Pause or edit anytime", ""]],
    upNext: [{ k: "watching", t: "Watching for the next paycheck", s: "The rule runs only after confirmed payroll posts to Yoshi Cash." }],
    record: "6 runs · $3,000.00 total",
    runs: [{ d: "Today", k: "ran", t: "Moved payday amount to Reserve", amt: "$500.00" }] },
  { id: "a9", name: "Invest on payday", status: "active", cadence: "When payroll lands", measure: "$300.00", sub: "invested today", mode: "auto",
    runTx: { icon: "trade", category: "Investment", accountScope: "yoshi", detail: "Payday investing · VTI", sign: -1 },
    now: { t: "Completed", s: "ran today · $300.00" },
    summary: "Invests $300.00 in VTI after a confirmed paycheck lands in Yoshi Cash.", next: "Next run · when payroll lands",
    rule: [["WHEN", "A paycheck lands in Yoshi Cash", "Confirmed posted payroll only", ""], ["DO", "Buy $300.00 of VTI", "Yoshi Brokerage ••4410 · market order", ""], ["LIMITS", "Max $300.00 per paycheck", "Skips if Cash would fall below $2,000.00", ""], ["MODE", "Runs automatically", "Pause or edit anytime", ""]],
    upNext: [{ k: "watching", t: "Watching for the next paycheck", s: "The rule runs only after confirmed payroll posts to Yoshi Cash." }],
    record: "6 runs · $1,800.00 total",
    runs: [{ d: "Today", k: "ran", t: "Bought VTI from payday", amt: "$300.00" }] },
];

/* ---------- coming up · the live queue of money instructions --------------
   One list for everything Yoshi is about to do — or is doing — with money.
   Whether it came from a screen, a brief approval, or a standing rule, it's
   the same object moving through the same lifecycle.
   kind:  "once"      — a one-time instruction (queued trade, pending transfer)
          "recurring" — the next run of a standing automation (skippable)
          "predicted" — an expected event (payday, a subscription) — nothing
                        to cancel, so rows stay passive
   state: "working"   — at the venue now; cancel is a race
          "pending"   — submitted to the rails; past the point of no return
          "queued"    — waiting for its trigger; cancel is free
          "scheduled" — a rule's next occurrence
          "predicted" */
// Market-hours is the default trade demo. Add ?market=closed to keep exercising
// the queued-order path without hiding the live execution flow.
const MARKET_OPEN = new URLSearchParams(window.location.search).get("market") !== "closed";
const INSTANT_FUNDING_CAP = 10000;
const INFLIGHT_RANK = { working: 0, pending: 1, queued: 2, scheduled: 3, predicted: 4 };

const COMING_UP_SEED = [
  /* This sell already executed. Its proceeds increase Trade's Available amount
     immediately, but stay out of Transfer until settlement. */
  { id: "f-sale", icon: "trade", title: "Sold VTI", category: "Trade", kind: "once", state: "pending",
    agent: false, when: "Executed · settles tomorrow", occurredOn: "Today", time: "8:48 AM", occurredAt: "2026-05-29T08:48:00-07:00", chip: "Executed · settlement pending", net: 3000.00, unsettledSaleProceeds: 3000.00, scope: "yoshi",
    acct: "One-time order · Yoshi Brokerage", trigger: "Settlement · tomorrow",
    steps: [["Executed", "Today · 8:48 AM"], ["Settlement pending", "You are here"], ["Settles", "Tomorrow"]], stepAt: 1 },
  /* The earlier $3,000 deposit consumes the first part of the shared $10,000
     instant-funding limit, leaving $7,000 for this later $20,000 deposit. */
  { id: "f2", icon: "arrow-down-left", title: "Chase Checking", category: "Transfer", kind: "once", state: "pending",
    agent: false, when: "Pending · in transit", occurredOn: "May 28", time: "6:40 PM", occurredAt: "2026-05-28T18:40:00-07:00", chip: "Pending · in transit", net: 20000.00, instantAvailable: 7000.00, pendingAmount: 13000.00, scope: "yoshi",
    rail: "Standard ACH", acct: "Yoshi Cash",
    steps: [["Recorded", "Yesterday · 6:40 PM"], ["Pending at the bank network", "You are here"], ["Clears", "when received"]], stepAt: 1 },
  { id: "f2-full", icon: "arrow-down-left", title: "Chase Checking", category: "Transfer", kind: "once", state: "pending",
    agent: false, when: "Pending · in transit", occurredOn: "May 28", time: "6:10 PM", occurredAt: "2026-05-28T18:10:00-07:00", chip: "Pending · in transit", net: 3000.00, instantAvailable: 3000.00, pendingAmount: 0, scope: "yoshi",
    rail: "Standard ACH", acct: "Yoshi Cash",
    steps: [["Recorded", "Yesterday · 6:10 PM"], ["Pending at the bank network", "You are here"], ["Clears", "when received"]], stepAt: 1 },
  { id: "u1", icon: "calendar-clock", title: "Weekly investing · VTI", category: "Automation", kind: "recurring", state: "scheduled",
    agent: true, when: "Fri, May 30", chip: "Runs Fri · market open", net: -500.00, scope: "yoshi", autoId: "a1", rail: "Auto-invest", acct: "Yoshi Brokerage ••4410",
    nextWhen: "Fri, Jun 6", nextChip: "Runs Fri, Jun 6 · market open",
    skipNote: "Skipping affects only this run — the rule stays on and runs again the following Friday.",
    steps: [["Standing rule", "Approved · runs weekly"], ["Next run", "Fri · market open"], ["Posts to Activity", "with the fill price"]], stepAt: 1 },
  { id: "u2", icon: "calendar-clock", title: "Pay Chase Sapphire", category: "Automation", kind: "recurring", state: "scheduled",
    agent: true, when: "Tue, Jun 2", chip: "Runs Tue, Jun 2", net: -2340.18, scope: "yoshi", autoId: "a5", acct: "Cash → Chase Sapphire", rail: "ACH",
    nextWhen: "Wed, Jul 2", nextChip: "Runs Wed, Jul 2",    skipNote: "Skipping affects only this run — interest may accrue if the statement isn't paid another way.",
    steps: [["Standing rule", "Approved · on statement close"], ["Next run", "Tue, Jun 2"], ["Posts to Activity", "when it clears"]], stepAt: 1 },
  { id: "u3", icon: "banknote", title: "Acme Corp", category: "Income", kind: "predicted", state: "predicted",
    agent: false, when: "Monday, Jun 15", net: 6240.00, scope: "external", acct: "Direct deposit" },
  { id: "u4", icon: "credit-card", title: "Netflix", category: "Subscriptions", kind: "predicted", state: "predicted",
    agent: false, when: "Thursday, Jun 25", net: -15.49, scope: "card", acct: "Card ··4417" },
];

/* ---------- market universe + explore (trade search) ---------------------- */
const MARKET = [
  ...HOLDINGS.map(h => ({ id: h.id, ticker: h.ticker, name: h.name, last: h.last, dch: h.dch, kind: h.kind, held: true })),
  { id: "tsla",  ticker: "TSLA",  name: "Tesla Inc",            last: 342.18, dch: -1.84, kind: "stock" },
  { id: "googl", ticker: "GOOGL", name: "Alphabet Inc",         last: 178.22, dch: 0.91,  kind: "stock" },
  { id: "amzn",  ticker: "AMZN",  name: "Amazon.com Inc",       last: 201.40, dch: 1.22,  kind: "stock" },
  { id: "spy",   ticker: "SPY",   name: "SPDR S&P 500 ETF",     last: 597.10, dch: 0.64,  kind: "etf" },
  { id: "coin",  ticker: "COIN",  name: "Coinbase Global",      last: 248.77, dch: 3.10,  kind: "stock" },
  { id: "qqq",   ticker: "QQQ",   name: "Invesco QQQ Trust",    last: 512.33, dch: 0.88,  kind: "etf" },
  { id: "meta",  ticker: "META",  name: "Meta Platforms",       last: 612.05, dch: 1.45,  kind: "stock" },
  { id: "amd",   ticker: "AMD",   name: "Advanced Micro Dev",   last: 168.90, dch: 2.07,  kind: "stock" },
  { id: "doge",  ticker: "DOGE",  name: "Dogecoin",             last: 0.42,   dch: 5.60,  kind: "crypto" },
];

const MARKET_QUOTE_OVERRIDES = {
  VTI: { volume: "3.7M", bidSize: 700, askSize: 500 },
  NVDA: { volume: "14.2M", bidSize: 400, askSize: 200 },
  AAPL: { volume: "47.8M", bidSize: 900, askSize: 500 },
  VOO: { volume: "5.6M", bidSize: 300, askSize: 400 },
  MSFT: { volume: "18.9M", bidSize: 600, askSize: 300 },
  BTC: { volume: "$31.8B", bidSize: 2, askSize: 3 },
  ETH: { volume: "$14.6B", bidSize: 18, askSize: 12 },
  SOL: { volume: "$4.2B", bidSize: 80, askSize: 60 },
  TSLA: { volume: "89.4M", bidSize: 500, askSize: 800 },
};

const securityMarketQuote = (security) => {
  const selected = typeof security === "string"
    ? MARKET.find((item) => item.id === security || item.ticker === security.toUpperCase())
    : security;
  const last = selected?.last || 0;
  const cryptoTick = last >= 1000 ? 1 : last >= 1 ? 0.1 : 0.001;
  const tick = selected?.kind === "crypto" ? cryptoTick : 0.02;
  const decimals = last < 1 ? 4 : 2;
  const override = MARKET_QUOTE_OVERRIDES[selected?.ticker] || {};
  return {
    volume: override.volume || (selected?.kind === "crypto" ? "$2.8B" : selected?.kind === "etf" ? "8.6M" : "21.4M"),
    bid: +Math.max(0, last - tick).toFixed(decimals),
    bidSize: override.bidSize || 400,
    ask: +(last + tick).toFixed(decimals),
    askSize: override.askSize || 200,
    // Illustrative QUODD-shaped fields; no live market data is requested.
    lastSize: 100,
    lastMarket: selected?.kind === "crypto" ? null : selected?.kind === "etf" ? "ARCX" : "FINN",
    bidMarket: selected?.kind === "crypto" ? null : "BATS",
    askMarket: selected?.kind === "crypto" ? null : "XNYS",
    asOfDate: "Oct 5, 2026",
    snapshotTime: "3:11:14 PM",
    lastTime: "3:11:12 PM",
    bidTime: "3:11:13 PM",
    askTime: "3:11:13 PM",
    decimals,
  };
};

const securityHolding = (sec) => {
  const id = typeof sec === "string" ? sec : sec?.id;
  const ticker = typeof sec === "string" ? sec.toUpperCase() : sec?.ticker;
  return HOLDINGS.find((h) => h.id === id || h.ticker === ticker) || null;
};
const securityYearReturn = (sec) => {
  const h = securityHolding(sec);
  if (h?.perf?.["1Y"] != null) return h.perf["1Y"];
  const m = typeof sec === "string" ? MARKET.find((x) => x.id === sec || x.ticker === sec.toUpperCase()) : sec;
  return m?.kind === "crypto" ? 72 : m?.kind === "etf" ? 17 : 31;
};
const securityFundamentals = (sec) => {
  const h = securityHolding(sec);
  const m = h || (typeof sec === "string" ? MARKET.find((x) => x.id === sec || x.ticker === sec.toUpperCase()) : sec);
  const fund = h?.fund || [];
  const get = (label) => fund.find(([k]) => k === label)?.[1];
  const asset = m?.kind === "crypto" ? "Crypto" : m?.kind === "etf" ? "ETF" : "Equity";
  const marketCap = get("Market cap") || (m?.kind === "crypto" ? "$1.90T" : m?.kind === "etf" ? "$1.35T" : "$" + ((m?.last || 100) * 2.4).toFixed(2) + "B");
  const metric = get("Expense ratio") ? ["Expense ratio", get("Expense ratio")] :
    get("P/E ratio") ? ["P/E ratio", get("P/E ratio")] :
    m?.kind === "crypto" ? ["Network", get("Network") || m.name] :
    ["P/E ratio", "31.4"];
  return [["Asset class", get("Asset class") || asset], ["Market cap", marketCap], metric, ["1 year return", pct(securityYearReturn(m))]];
};
const securityTake = (sec) => {
  const h = securityHolding(sec);
  const m = h || (typeof sec === "string" ? MARKET.find((x) => x.id === sec || x.ticker === sec.toUpperCase()) : sec);
  const t = m?.ticker || "this name";
  const held = !!h;
  const named = {
    NVDA: held ? ["Your standout winner — and now your largest single position. I'd consider trimming a slice to lock in some of that gain and keep one name from steering the whole portfolio.", `trim some ${t} to lock gains`] : ["NVIDIA is high-quality, but volatile after a huge run. I'd size any new position carefully instead of chasing a green day.", `size a starter position in ${t}`],
    AAPL: held ? ["Steady ballast that's doing its job. Nothing here needs a decision today — hold and let it compound.", `review my ${t} position`] : ["Apple is a mature compounder. Worth comparing against what you already own before adding another mega-cap position.", `compare ${t} to my portfolio`],
    MSFT: held ? ["Quietly compounding. A reasonable place to add on a dip; otherwise just hold.", `set up a buy-the-dip rule for ${t}`] : ["Microsoft is a durable large-cap name. I'd treat it as a core equity candidate, not a quick trade.", `analyze ${t} as a long-term holding`],
    VOO: held ? ["Your low-cost index core. Keep the auto-invest running — this is the boring engine that does most of the work.", `review my ${t} auto-invest`] : ["VOO is broad, low-cost S&P 500 exposure. It's usually a clean default when you want diversified US equity instead of picking single stocks.", `set up recurring buys for ${t}`],
    VTI: held ? ["Total-market index, and it's in your Roth — exactly where it belongs, since the gains compound tax-free. Let it ride.", `review my Roth allocation`] : ["VTI is broad total-market exposure. Good if you want one fund to cover the US market instead of choosing sectors.", `compare ${t} and VOO`],
    BTC: held ? ["Up strongly from your cost, and your most volatile sleeve. I'd size it deliberately so a rough week doesn't rattle you — trimming toward a target weight is the clean move.", `set a target weight for ${t}`] : ["Bitcoin is volatile, so the main decision is position size. I'd only add it if you're clear on the role it plays in your mix.", `set a target weight for ${t}`],
    ETH: held ? ["Held at a gain but choppier than Bitcoin. Fine as a smaller satellite; I wouldn't chase it higher from here.", `rebalance my crypto`] : ["Ethereum can work as a smaller crypto satellite, but I'd size it below your core holdings.", `compare ETH and BTC`],
    SOL: held ? ["A big run on a high-beta name. Taking a little profit into your cash buffer locks some of it in without trying to call the top.", `take some ${t} profit into cash`] : ["Solana is high beta even by crypto standards. I'd treat it as a small satellite, not a core position.", `size a small ${t} position`]
  };
  if (named[t]) return { take: named[t][0], cue: named[t][1] };
  if (m?.kind === "crypto") return { take: held ? `${t} is a volatile sleeve. Worth sizing on purpose rather than adding on momentum.` : `${t} is volatile, so start with sizing and downside tolerance before buying.`, cue: `review ${t}` };
  if (m?.kind === "etf") return { take: `${t} is diversified index exposure — a sensible core you can add to on a schedule.`, cue: `automate adding to ${t}` };
  return { take: held ? `${t} is working for you. No need to touch it unless it has grown into too large a slice of the portfolio.` : `${t} could be a new single-name position. I'd compare it against your current concentration before buying.`, cue: `check whether ${t} fits my portfolio` };
};
const TRENDING = ["nvda", "btc", "tsla", "sol", "coin", "amd"];
const EXPLORE_NEWS = [
  ["Bloomberg", "Chipmakers lead the tape as AI demand holds", "41m"],
  ["Reuters",   "Fed minutes point to one more cut this year",  "2h"],
  ["CoinDesk",  "Spot BTC ETFs log eighth straight inflow day", "3h"],
  ["WSJ",       "Big tech earnings beat, guidance mixed",        "5h"],
];

/* AI news brief · whole-market synthesis with cited, click-out sources */
const MARKET_NEWS = {
  when: "9:41 AM",
  brief: [
    { t: "Equities opened higher with semiconductors leading the tape as AI demand stays firm", s: 0 },
    { t: "Rate worries eased after Fed minutes pointed to one more cut this year", s: 1 },
    { t: "Crypto held steady — spot Bitcoin ETFs logged an eighth straight day of inflows", s: 2 },
    { t: "Big-tech earnings beat on revenue, though forward guidance came in mixed", s: 3 },
  ],
  sources: [
    { pub: "Bloomberg", head: "Chipmakers lead the tape as AI demand holds", when: "41m", url: "https://www.bloomberg.com/markets" },
    { pub: "Reuters",   head: "Fed minutes point to one more cut this year",  when: "2h",  url: "https://www.reuters.com/markets/us/" },
    { pub: "CoinDesk",  head: "Spot BTC ETFs log eighth straight inflow day", when: "3h",  url: "https://www.coindesk.com/markets" },
    { pub: "WSJ",       head: "Big tech earnings beat, guidance mixed",        when: "5h",  url: "https://www.wsj.com/finance/stocks" },
  ],
};

/* AI news brief · synthesis of stories that touch the user's holdings */
const HOLDINGS_NEWS = {
  when: "9:41 AM",
  brief: [
    { tkr: "NVDA", t: "extended its rally on steady AI demand, lifting your largest position", s: 0 },
    { tkr: "VTI",  t: "rides a record day of broad-market ETF inflows — a quiet tailwind", s: 1 },
    { tkr: "BTC",  t: "held a key level, steadying your crypto sleeve; nothing needs action", s: 2 },
  ],
  sources: [
    { pub: "Bloomberg",   head: "Nvidia extends rally as AI orders stay strong", when: "1h", url: "https://www.bloomberg.com/quote/NVDA:US" },
    { pub: "Morningstar", head: "Broad-market ETF inflows hit a record",         when: "3h", url: "https://www.morningstar.com/etfs/arcx/vti/quote" },
    { pub: "CoinDesk",    head: "Bitcoin holds support as ETF demand steadies",  when: "4h", url: "https://www.coindesk.com/price/bitcoin" },
  ],
};

/* ---------- profile -------------------------------------------------------- */
const PROFILE = {
  name: "Rivka Lipson", handle: "@rivka", member: "Member since 2025", email: "rivka@yoshi.ai",
  channels: [
    ["Push notifications", "On this device", true],
    ["Text", "+1 ••• ••• 4471", true],
  ],
  limits: [
    ["Per-approval cap", "$25,000.00"],
    ["Daily autonomous cap", "$5,000.00"],
    ["Trades without approval", "Off"],
  ],
};

/* ---------- "For you" · the notification hub, split into folders ----------- */
/* folder: "insights" (recaps, reports, opportunities, nudges, price alerts) or
   "automations" (confirmations a standing rule ran). Operational failures live
   in ERROR_ALERTS inside briefs.jsx. "Needs you" pulls from PROPOSALS. */
const BRIEFS = [
  { id: "b-automation-move-review", briefKey: "INV_AUTOMATION_MOVE_REVIEW", folder: "insights", type: "automation", title: "NVDA is down $22.47 (12.40%) today", ts: 91201.1, when: "August 24, 2026 · 2:38 PM EDT", automationMoveReview: {
    ticker: "NVDA",
    driver: "NVIDIA cut its revenue outlook after a customer delay. Your position is worth $28,578.60, down $4,044.60.",
    evidence: { current: true, asOf: "2026-08-24T14:38:00-04:00", quote: 158.77, priorClose: 181.24, positionValue: 28578.60, positionChange: -4044.60, explanationVerified: true, reviewWindowAvailable: true, otherReviewOpen: false },
    automations: [{
      id: "nvda-daily-approved", ticker: "NVDA", title: "Buy $500.00 of NVDA every market day", amount: 500,
      account: "Yoshi Brokerage", displayAccount: "Yoshi Brokerage ••4410", scheduleId: "market-daily", status: "active", current: true, userApproved: true, moneyMoving: true,
      beingModified: false, receivedThisSession: false, concentrationLimit: 25,
      schedules: [
        { id: "market-daily", short: "Every market day", cardCadence: "each market day at the open", cardNext: "Aug 25 at 9:30 AM ET", cadence: "Every market day at 9:30 AM EDT", next: "August 25, 2026 at 9:30 AM EDT", nextISO: "2026-08-25T09:30:00-04:00" },
        { id: "weekly-friday", short: "Every Friday", request: "weekly", cadence: "Every Friday at 9:30 AM EDT, skipping market holidays", next: "August 28, 2026 at 9:30 AM EDT", nextISO: "2026-08-28T09:30:00-04:00" },
        { id: "monthly", short: "Monthly", request: "monthly", cadence: "First market day of each month at 9:30 AM EDT", next: "September 1, 2026 at 9:30 AM EDT", nextISO: "2026-09-01T09:30:00-04:00" },
        { id: "biweekly-friday", short: "Every other Friday", request: "every other Friday", cadence: "Every other Friday at 9:30 AM EDT, starting August 28, 2026; skip market holidays", next: "August 28, 2026 at 9:30 AM EDT", nextISO: "2026-08-28T09:30:00-04:00" }
      ]
    }]
  } },

  { id: "b-price-move", briefKey: "INV_PRICE_MOVE_10", folder: "insights", type: "alert", title: "NVDA is down −$22.47 (−12.40%) today, to $158.77", ts: 91201, body: "NVIDIA lowered its near-term data-center revenue outlook after a delayed customer deployment.", when: "Today · 2:38 PM", holdingEvent: {
    driver: "NVIDIA lowered its near-term data-center revenue outlook after a delayed customer deployment.",
    actionPrompt: "Want me to prepare buying $7,144.50 of NVDA, which is 25.00% more than your current $28,578.00 position? You currently have $12,400.00 available in cash.",
    ongoingNote: "Let me know if you don't want me to send you these in the future.",
  } },
  { id: "b-price-move-up", briefKey: "INV_PRICE_MOVE_10", folder: "insights", type: "alert", title: "NVDA is up +$22.47 (+12.40%) today, to $203.71", ts: 91200.75, body: "NVIDIA raised its revenue outlook after stronger-than-expected demand for its newest data-center systems.", when: "Today · 2:31 PM", holdingEvent: {
    driver: "NVIDIA raised its revenue outlook after reporting stronger-than-expected demand for its newest data-center systems.",
    actionPrompt: "Want me to prepare selling $9,166.95 of NVDA, which is 25.00% of your current $36,667.80 position?",
    gainLossTitle: "If you sell $9,166.95",
    gainLoss: [["Long-term gain", "+$2,214.00"], ["Short-term loss", "−$318.00"]],
    ongoingNote: "Let me know if you don't want me to send you these in the future.",
  } },
  { id: "b-portfolio-update-daily", briefKey: "INV_PORTFOLIO_UPDATE", folder: "insights", type: "report", title: "Today’s portfolio recap", ts: 91200.5,
    body: "Your portfolio gained $3,142.00 (+2.60%) today. SPY, the S&P 500 ETF, gained $10.12 (+1.80%) to $572.35.", when: "Today · 4:11 PM",
    portfolioUpdateDaily: {
      opening: "Your portfolio gained $3,142.00 (+2.60%) today. SPY, the S&P 500 ETF, gained $10.12 (+1.80%) to $572.35. Stocks rose after a cooler-than-expected inflation report strengthened expectations for a September interest-rate cut. Technology shares led the market higher.",
      drivers: [
        { performance: ["GOOGL", "Alphabet", "+$561.17", "+4.50%"], news: "Alphabet rose as analysts raised cloud-revenue estimates ahead of Thursday’s earnings report." },
        { performance: ["NVDA", "NVIDIA", "+$352.00", "+3.40%"], news: "NVIDIA extended its rally as demand for its newest AI systems remained strong." },
        { performance: ["AAPL", "Apple", "−$151.00", "−0.90%"], news: "Apple slipped after new smartphone shipment estimates pointed to softer demand in China." },
      ],
      takeaways: [
        "You bought 20.00 NVDA shares at $181.24 for $3,624.80. That increased NVDA exposure by $3,624.80 (+3.30 percentage points), from 5.10% to 8.40%.",
        "GOOGL, currently $12,389.00 (9.80%) of your portfolio, reports earnings Thursday after the close.",
        "Your three largest holdings total $39,190.00 (31.00%) of your portfolio.",
      ],
      cadenceNote: "I’ll send this daily going forward. Let me know if you’d prefer weekly, monthly, or not at all."
    } },
  { id: "b-weekly-financial-review-causal", briefKey: "BNK_ACCOUNT_REVIEW", folder: "insights", type: "report", title: "Your weekly financial review", recurringTitle: "Your weekly financial review", ts: 91001,
    body: "This week, investments gained $1,284.00 (+0.34%), led by NVDA; cash increased $312.00 (+0.38%) because deposits exceeded bills and transfers; and debt fell $1,160.00 (−20.90%) after your Amex payment.",
    when: "Friday · 4:11 PM",
    weeklySnapshot: [["Performance", "+$1,284.00", "+0.34% this week", "pos"], ["Interest earned", "+$52.31", "at 3.30% APY", "pos"], ["Cash flow", "+$312.00", "after bills", "pos"]],
    weeklySectionLabel: "Snapshot",
    financialEvents: ["Biggest mover: NVDA led your portfolio after expanded Blackwell availability with cloud partners.", "Cash flow and spending: Your weekday deposit covered the week's bills. Dining and convenience purchases ran above your usual pace.", "Debt: Your Amex Gold balance fell $1,160.00 this month, which cuts your interest cost by about $23.00 a month."],
    weeklyHandled: ["Yoshi bought VTI and AAPL after cash became available, adding to your invested balance.", "Yoshi kept your cash above the working reserve while bills and transfers cleared."],
    cadenceNote: "I’ll send this weekly going forward. Let me know if you’d prefer monthly, quarterly, or not at all.",
    suppressWhenEmpty: true,
    ask: ["Explain this review.", "This review connects the week’s changes to their main drivers. It is informational and does not make a financial move on its own."] },
  { id: "b-investor-profile-new", briefKey: "INV_INVESTOR_PROFILE", folder: "insights", type: "profile", kind: "insight", icon: "doc", title: "Help me personalize your investment guidance", ts: 91002.24,
    body: "I can make investment updates and suggestions more useful to you when I understand what you’re investing for, when you’ll need the money, and how you feel about risk. Since you’re new to Yoshi, I need a few answers to start building your investor profile.", when: "Today · 8:05 AM",
    investorProfile: {
      known: [],
      knownNote: "",
      questions: [
        { id: "investment-objective", prompt: "What are you investing for?", options: ["Long-term growth", "Income", "Preserve capital", "A specific goal"] },
        { id: "time-horizon", prompt: "When do you expect to need most of this money?", options: ["Under 3 years", "3–7 years", "7–10 years", "More than 10 years"] },
        { id: "liquidity-needs", prompt: "How much of this money needs to stay easily accessible?", options: ["Low — no near-term need", "Moderate", "High — may need it soon"] },
        { id: "risk-tolerance", prompt: "How much investment risk are you comfortable taking?", options: ["Conservative", "Moderate", "Aggressive"] },
        { id: "loss-response", prompt: "What would you most likely do if your investments fell 20.00%?", options: ["Buy more", "Stay invested", "Sell some", "Sell most"] },
        { id: "investment-experience", prompt: "How much investing experience do you have?", options: ["I’m new", "Some experience", "Experienced", "Very experienced"] },
        { id: "liquid-net-worth", prompt: "What is your approximate liquid net worth?", options: ["Under $50,000", "$50,000–$249,999", "$250,000–$999,999", "$1,000,000–$4,999,999", "$5,000,000+"] },
        { id: "investment-constraints", prompt: "Are there investments, industries, or strategies you want me to avoid?", options: ["No restrictions", "Specific industries", "Individual stocks", "Tell Yoshi in chat"] },
      ],
      optionalPrompt: "You can also tell me in chat about any upcoming expenses, investments to avoid, or anything else I should consider."
    },
    ask: ["Help me build my investor profile.", "I’ll use your answers to make future investment guidance more relevant to your goals and circumstances."] },
  { id: "b-payday-with-automations", briefKey: "BNK_PAYDAY", folder: "insights", type: "report", title: "You got paid $6,240.00 today", ts: 91002,
    body: "Your $6,240.00 paycheck from Acme Corp landed in Yoshi Cash today. $500.00 moved to Reserve, $300.00 was invested in VTI, and the remaining $5,440.00 stayed in cash.", when: "Today · 8:02 AM",
    payday: {
      source: "Acme Corp", destination: "Yoshi Cash ••8841", amount: "$6,240.00", posted: "Today at 8:02 AM",
      automations: [{ automationId: "a8", title: "Move $500.00 to Reserve", outcome: "Completed today" }, { automationId: "a9", title: "Invest $300.00 in VTI", outcome: "Completed today" }],
      closing: "Let me know if you want me to set up more payday automations."
    } },
  { id: "b-payday-without-automations", briefKey: "BNK_PAYDAY", folder: "insights", type: "report", title: "You got paid $6,240.00 today", ts: 91001.5,
    body: "Your $6,240.00 paycheck from Acme Corp landed in Yoshi Cash today.", when: "Today · 8:02 AM",
    payday: {
      source: "Acme Corp", destination: "Yoshi Cash ••8841", amount: "$6,240.00", posted: "Today at 8:02 AM", automations: [],
      closing: "Let me know if you want me to set up automations to route your paycheck automatically."
    } },
  { id: "b-debt-overview-personalized", briefKey: "BNK_DEBT_OVERVIEW", folder: "insights", type: "report", title: "Your credit and debt overview", ts: 91001.24,
    body: "I’ve automatically synced your debt accounts and credit profile into Yoshi, so you can see them together and pay eligible accounts here. Your credit score is 825, which is excellent. Your active card is paid in full and has low utilization, so its current balance is not debt to prioritize.", when: "Today · 8:04 AM",
    debtOverview: {
      intro: "I’ve automatically synced your debt accounts and credit profile into Yoshi, so you can see them together and pay eligible accounts here.",
      analysis: "Your credit score is 825, which is excellent. Your active card is paid in full and has low utilization, so its current balance is not debt to prioritize.",
      creditProfile: [["Credit score", "825", "Excellent"], ["Payment status", "No late flags", "Latest available profile"], ["Card utilization", "0.57%", "$441.63 of $77,100.00"], ["Revolving cards", "0", "One active card pays in full"]],
      creditSummary: "Your 825 credit score reflects a strong profile. Your active card is paid in full and has low utilization. No delinquency is reported for the active card or mortgage.",
      debts: [
        { name: "UBS mortgage", mask: "••6705", balance: "$1,953,066.00", apr: "Unknown APR", minimum: "$5,696.00 minimum", note: "In good standing · paid $6,498.00 Aug 1" },
        { name: "Land Rover lease", mask: "••5717", balance: "$18,034.00", apr: "5.77% APR", minimum: "$1,036.00 minimum", note: "Paid $1,036.00 Aug 3" },
        { name: "United MileagePlus Club", mask: "••4810", balance: "$441.63", apr: "21.99%-28.99% APR", minimum: "$0.00 minimum", note: "Paid in full · 0.57% utilization" },
      ],
      actionPrompt: "Do you know the interest rate on your UBS mortgage? That will help me give you better advice and track it over time.",
      actionBoundary: "Nothing will move unless you review and approve a payment."
    } },
  { id: "b-debt-overview-personalized-895c", briefKey: "BNK_DEBT_OVERVIEW", folder: "insights", type: "report", title: "Your credit and debt overview", ts: 91001.23,
    body: "I’ve automatically synced your debt accounts and credit profile into Yoshi, so you can see them together and pay eligible accounts here. Your credit score is 825, which is excellent. Your mortgage and auto loan are current, and Chase Sapphire Reserve is paid in full. I need you to verify one American Express account before I can tell whether its balance should be prioritized.", when: "Today · 8:04 AM",
    debtOverview: {
      intro: "I’ve automatically synced your debt accounts and credit profile into Yoshi, so you can see them together and pay eligible accounts here.",
      analysis: "Your credit score is 825, which is excellent. Your mortgage and auto loan are current, and Chase Sapphire Reserve is paid in full. I need you to verify one American Express account before I can tell whether its balance should be prioritized.",
      creditProfile: [["Credit score", "825", "Excellent"], ["Payment status", "No late flags", "Latest available profile"], ["Card utilization", "23.51%", "$35,196.81 of $149,700.00"], ["Revolving cards", "0", "One verified card pays in full"]],
      creditSummary: "Your 825 credit score reflects a strong profile. The verified debts shown here are current, and the Chase card is paid in full. The American Express payment pattern still needs your confirmation.",
      debts: [
        { name: "Mortgage loan", mask: "••2267", balance: "$5,335,000.00", apr: "5.25% APR", minimum: "$23,340.63 minimum", note: "Paid $23,340.63 Jul 15" },
        { name: "SELCO auto loan", mask: "••0201", balance: "$39,917.00", apr: "4.28% APR", minimum: "$1,124.00 minimum", note: "Paid $1,124.00 Jun 1" },
        { name: "Chase Sapphire Reserve", mask: "••3559", balance: "$18,500.81", apr: "21.49%-28.49% APR", minimum: "$151.00 minimum", note: "Paid in full · not revolving" },
        { name: "American Express", mask: "••4006", balance: "$16,696.00", apr: "18.24%-29.24% APR", minimum: "$164.38 minimum", note: "Payment pattern needs verification", verification: { label: "Verify", tone: "accent" } },
      ],
      actionPrompt: "I couldn’t confirm the payment pattern for American Express ••4006. Verify this account so I can determine whether its balance should be prioritized.",
      actionBoundary: "Verification updates the account details. It does not move money."
    } },
  { id: "b-debt-overview-personalized-2d6b", briefKey: "BNK_DEBT_OVERVIEW", folder: "insights", type: "report", title: "Your credit and debt overview", ts: 91001.22,
    body: "I’ve automatically synced your debt accounts and credit profile into Yoshi, so you can see them together and pay eligible accounts here. Your credit score is 825, which is excellent. The cards whose payment patterns are confirmed are paid in full. I need you to verify one American Express account before I can tell whether its balance should be prioritized.", when: "Today · 8:04 AM",
    debtOverview: {
      intro: "I’ve automatically synced your debt accounts and credit profile into Yoshi, so you can see them together and pay eligible accounts here.",
      analysis: "Your credit score is 825, which is excellent. The cards whose payment patterns are confirmed are paid in full. I need you to verify one American Express account before I can tell whether its balance should be prioritized.",
      creditProfile: [["Credit score", "825", "Excellent"], ["Payment status", "No late flags", "Latest available profile"], ["Card utilization", "8.44%", "Highest reported active card"], ["Revolving cards", "0", "Three verified cards pay in full"]],
      creditSummary: "Your 825 credit score reflects a strong profile. The verified active cards are paid in full, and no delinquency is reported. The American Express payment pattern still needs your confirmation.",
      debts: [
        { name: "American Express", mask: "••1007", balance: "$4,685.00", apr: "18.24%-29.24% APR", minimum: "Unknown minimum", note: "Payment pattern needs verification", verification: { label: "Verify", tone: "accent" } },
        { name: "Capital One", mask: "••6871", balance: "$133.39", apr: "21.49%-29.99% APR", minimum: "$0.00 minimum", note: "Paid in full · not revolving" },
        { name: "Chase Sapphire Preferred", mask: "••6449", balance: "$414.59", apr: "21.49%-28.49% APR", minimum: "$40.00 minimum", note: "Paid in full · not revolving" },
        { name: "Chase Sapphire Reserve", mask: "••9962", balance: "$876.00", apr: "21.49%-28.49% APR", minimum: "$40.00 minimum", note: "Paid in full · not revolving" },
      ],
      actionPrompt: "I couldn’t confirm the payment pattern for American Express ••1007. Verify this account so I can determine whether its balance should be prioritized.",
      actionBoundary: "Verification updates the account details. It does not move money."
    } },
  { id: "b-earnings-pre", folder: "insights", type: "earnings", title: "NVDA reports earnings tomorrow", ts: 90800,
    body: "NVDA reports tomorrow after market close. Analysts expect $45.80B in revenue, up $18.21B (+66.00%) year over year, and $0.87 in adjusted EPS, up $0.28 (+48.00%). The biggest questions are Blackwell supply, data-center margins, and next-quarter guidance. NVDA is your largest holding at 18.00% of your portfolio, so the results could have an outsized effect.", when: "Today · 8:00 AM",
    earningsPre: {
      position: { value: "$25,590.60", note: "180 shares", weight: "18.00%", rank: "Your largest holding" },
      consensus: [["Revenue", "$45.80B", "+$18.21B (+66.00%) year over year"], ["Adjusted EPS", "$0.87", "+$0.28 (+48.00%) year over year"]],
      watch: ["Blackwell supply and how quickly cloud customers can bring new systems online.", "Data-center gross margin, where the product mix can matter as much as the headline beat.", "Management’s next-quarter outlook and any change in hyperscaler demand commentary."],
      news: "NVDA’s latest updates pointed to continued AI infrastructure demand, with supply and margins still the key questions heading into results.",
      newsSource: "Sources · Reuters, Company filings"
    },
    ask: ["What should I watch in NVDA's earnings?", "I can walk through the expectations, the questions analysts will focus on, and how the result could matter to your portfolio."] },
  { id: "b-earnings-post", folder: "insights", type: "earnings", title: "NVDA reported earnings", ts: 90850,
    body: "The quarter was stronger than expected on revenue and guidance, while margins remained the main point of caution. NVDA is your largest holding at 18.00% of your portfolio, and its after-hours gain would add $1,202.40 (+4.70%) to your position if it holds through the open.", when: "Today · 5:20 PM",
    earningsPost: {
      market: { price: "$148.85", change: "+$6.68 (+4.70%)", label: "After hours" },
      position: { value: "$25,590.60", weight: "18.00%", rank: "Your largest holding" },
      results: [["Revenue", "$47.20B", "vs. $45.80B expected (+$1.40B, +3.06%)"], ["Adjusted EPS", "$0.94", "vs. $0.87 expected (+$0.07, +8.05%)"]],
      takeaways: ["Revenue and next-quarter guidance both came in ahead of the expectations shown before earnings.", "Gross margin remains the main watchpoint as the company ramps new systems and manages the product mix.", "The initial after-hours move is positive, but tomorrow’s trading can still turn on the call and follow-up questions."]
    },
    ask: ["What mattered in NVDA's earnings?", "I can walk through the results, compare them with the expectations from your pre-earnings Brief, and explain what the market is focusing on next."] },
  { id: "b-earnings-post-example", folder: "insights", type: "earnings", title: "Market example · MSFT reported earnings", ts: 90849,
    body: "Microsoft's quarter was stronger than expected on cloud revenue and guidance, while margins remained the main point of caution.", when: "Today · 5:20 PM",
    earningsPost: {
      marketExample: true, ticker: "MSFT", market: { price: "$478.14", change: "+$9.83 (+2.10%)", label: "After hours" },
      results: [["Revenue", "$76.40B", "vs. $74.20B expected (+$2.20B, +2.96%)"], ["Adjusted EPS", "$3.48", "vs. $3.36 expected (+$0.12, +3.57%)"]],
      takeaways: ["Revenue and forward guidance came in ahead of the expectations used in the pre-earnings market example.", "Azure demand remains the central driver, while cloud margins still depend on the balance between AI demand and infrastructure spending.", "The after-hours move is positive, but this market example does not assess your investments or suggest a trade."]
    },
    ask: ["What mattered in Microsoft's earnings?", "I can explain the result and the market context. Connect or add your portfolio when you want a Brief tailored to the securities you hold."] },
  { id: "b1", folder: "insights", type: "recap", title: "Market recap", ts: 81605,
    body: "Markets closed higher and your portfolio finished " + perf(DAY_ABS, DAY_PCT) + " — ahead of the S&P 500's +0.61% day. NVDA (+2.31%) drove most of the gain, which nudges your NVDA concentration a little further past its target band. Nothing needs action tonight — the concentration read sits right below this brief.",
    value: DAY_ABS, tone: "in", when: "Yesterday",
    visual: { kind: "spark", title: "Portfolio today", points: [0.00, 0.18, 0.26, 0.32, 0.48, 0.53, 0.71, 0.83, 0.98], metrics: [["Portfolio", "+$4,310.28", "+1.10%"], ["Top driver", "NVDA", "+2.31%"], ["Crypto sleeve", "BTC", "+3.42%"]] },
    detail: [
      "Stocks: +$2,102.68 — NVDA added $591.00 on data-center demand; VTI, VOO and MSFT each finished a few tenths higher, AAPL slipped 0.58% on trimmed hardware guidance.",
      "Crypto: +$2,207.60 — BTC +3.42% on an eighth straight day of spot-ETF inflows, SOL +4.77% on record network activity; ETH was the only decliner at −1.18%.",
      "Cash: flat by design — Cash and Reserve kept accruing at 3.30% APY.",
      "Context: S&P 500 +0.61%, Nasdaq +0.98% — your tech and crypto tilt is why you outran the index today.",
    ],
    ask: ["Give me today's market recap.", "Markets closed higher and you finished " + perf(DAY_ABS, DAY_PCT) + ", ahead of the S&P's +0.61%. NVDA (+2.31%) drove most of it. The only thing I'm tracking out of today is NVDA concentration creeping further past your 10.00% band — say the word and I'll size a rebalance."] },
  { id: "b2", folder: "insights", type: "report", title: "Debt payoff report", ts: 70800,
    body: "Card balances fell $1,160.00 this month to $4,391.00, which cuts your interest cost by about $23.00 a month. At $300.00 a month the Amex Gold (24.24% APR) clears in roughly 4 months, with the Sapphire close behind. One tweak worth making: shifting $100.00 a month of the Sapphire payment to the Amex clears it five weeks sooner and saves about $61.00 in interest.",
    value: 1160, tone: "in", when: "Jun 2",
    visual: { kind: "bars", title: "Card payoff progress", bars: [{ label: "Last month", value: 5551, display: "$5,551.00" }, { label: "Today", value: 4391, display: "$4,391.00" }], metrics: [["Month change", "-$1,160.00", "card debt"], ["Highest APR", "24.24%", "Amex Gold"], ["ETA", "4 months", "$300.00/mo"]] },
    detail: [
      "Amex Gold: $1,180.56 at 24.24% APR — the expensive one; every extra $100.00 here saves about $24.00 a year.",
      "Chase Sapphire: $3,210.44 at 21.99% APR — the statement auto-pays in full via your rule, so no new interest accrues there.",
      "Paid this month: $1,160.00 of principal across both cards, $0.00 in new interest charges.",
      "After payoff: the same $300.00 a month redirected to investing covers about half a year's Roth IRA limit.",
    ],
    ask: ["Retarget my payments to the Amex.", "Good call — it's the higher rate. I'll draft the change: $200.00 a month to the Sapphire, $400.00 to the Amex Gold until it clears (about 3 months), then everything back to the Sapphire. You'll approve it as a rule change before anything moves."] },
  { id: "b3", folder: "insights", type: "opportunity", title: "Idle cash is sitting still", ts: 81102,
    body: "$15,000.00 has been sitting in Cash for 9 days — that's above your buffer target, earning 3.30% (about $41.00 a month). In 26-week Treasuries near 5.10% the same money earns roughly $64.00 a month and stays reachable, so doing nothing costs you about $270.00 over the next year. I can draft the ladder now: six buys, staggered so a rung matures every few weeks.",
    when: "Yesterday",
    visual: { kind: "grid", title: "Idle cash evidence", metrics: [["Amount", "$15,000.00", "above buffer, 9 days"], ["Earning now", "3.30%", "≈ $41.00/mo"], ["Treasuries", "5.10%", "≈ $64.00/mo"], ["Cost of waiting", "$270.00", "next 12 months"]] },
    detail: [
      "The ladder: six $2,500.00 T-bill buys, staggered so one matures roughly every four weeks once it's rolling.",
      "Liquidity: any rung can be sold the next market day if you need cash sooner — small price movement is the only cost.",
      "Taxes: T-bill interest is exempt from state tax, so the effective edge over bank yield is slightly better than the headline.",
      "Guardrail: this only deploys the excess above your $15,000.00 Cash buffer — the buffer itself stays untouched.",
    ],
    ask: ["Draft the Treasury ladder.", "Drafting it: six $2,500.00 buys of 26-week T-bills, one settling each week starting tomorrow. You'll get one approval brief covering the series. Review the legs together, then approve or decline the series once; I'll skip any leg if your Cash would dip below the buffer."] },
  { id: "b4", folder: "insights", type: "nudge", title: "NVDA is 18.00% of your book", ts: 40900,
    body: "After a +64% year, NVDA is 18.00% of your investments — $25,590.60 riding on one name against your 10.00% target band. At this size a routine 10% pullback in NVDA is a $2,559.00 hit to your net worth; at target it would be about $1,422.00. Trimming roughly $11,400.00 into VTI halves the single-name risk, and because the position sits in your Roth IRA the rebalance is tax-free.",
    when: "May 30",
    visual: { kind: "bars", title: "Single-stock concentration", bars: [{ label: "NVDA current", value: 18, display: "18.00%" }, { label: "Target max", value: 10, display: "10.00%" }, { label: "Next largest", value: 8.10, display: "8.10%" }], metrics: [["Holding value", "$25,590.60", "Roth IRA"], ["Today", "+$591.00", "+2.31%"], ["Target gap", "+8.00%", "above band"]] },
    detail: [
      "Position: 180 shares at $142.17 against an $88.40 cost basis — up $9,678.60 (+60.8%) since you bought.",
      "Band math: your 10.00% cap works out to about $14,200.00 of NVDA — the trim to get there is ~$11,400.00 into VTI.",
      "Because it lives in your Roth IRA, rebalancing is tax-free — this is the cheapest account to fix concentration in.",
      "Prefer not to sell? Pointing new contributions at VTI instead closes the gap in about nine months without a single sale.",
    ],
    ask: ["Size the rebalance for me.", "Here it is: sell $11,400.00 of NVDA and buy $11,400.00 of VTI in the Roth — no tax impact, keeps ~$14,200.00 of NVDA so the thesis stays on. I'll send it as one approval brief; if you'd rather glide there without selling, say so and I'll retarget contributions instead."] },
  { id: "b5", folder: "insights", type: "alert", title: "Price alert · COIN", ts: 30900,
    body: "COIN crossed below your $240.00 alert, closing at $238.40 — down 2.10% on the week. You set this watching for an entry; you don't hold it, so nothing in your portfolio moved. If you still want in, a starter position under 1% of investments (about $3,300.00) keeps the risk contained — or I can move the alert down to $225.00 and keep waiting.",
    value: 240, when: "May 29",
    visual: { kind: "grid", title: "Price alert context", metrics: [["Trigger", "$240.00", "your alert level"], ["Close", "$238.40", "-2.10% this week"], ["Position", "$0.00", "not held"], ["Starter size", "$3,300.00", "≈ 1% of investments"]] },
    detail: [
      "Why it moved: crypto trading volumes cooled through the week, and COIN tracks them tightly.",
      "Your context: crypto is already 22.13% of your portfolio through BTC, SOL and ETH — COIN stacks equity risk on the same driver.",
      "Entry discipline: $3,300.00 is about 1.00% of investments — far inside your 10.00% single-name band.",
      "Compare COIN with your existing crypto exposure and consider whether the additional concentration fits your goals.",
    ],
    ask: ["Help me assess the risks.", "COIN adds company-specific risk alongside your existing crypto exposure. We can review the investment case, concentration, and downside before you decide whether to place an order."] },
  { id: "r1", folder: "automations", type: "automation", title: "Sweep idle cash ran", autoId: "a2",
    body: "Moved $4,000.00 from Cash to Reserve to keep earning 3.30% APY.",
    value: 4000, when: "9:30 AM" },
  { id: "r2", folder: "automations", type: "automation", title: "Weekly investing ran", autoId: "a1",
    body: "Bought $500.00 of VTI at the open. 11-week streak, +$184.00 so far.",
    value: 500, when: "Fri" },
  { id: "r3", folder: "automations", type: "automation", title: "Paid Chase Sapphire in full", autoId: "a5",
    body: "Paid the $2,340.00 statement from Cash the day before it was due. No interest owed.",
    value: 2340, when: "Jun 2" },
];

/* insights folder — what Yoshi sees across the accounts */
const BRIEF_INSIGHTS = [
  /* fired by the alert-mode spending-report rule — carries the same rule
     provenance chip as money briefs, so all three modes close one loop */
  { label: "Spending", stat: "−12.00%", tone: "pos", when: "Jun 1", ts: 60800, source: { auto: "a6", name: "Monthly spending report" }, read: "May spending came in at $6,184.00 — $841.00 less than April, and your first month under $6,500.00 since January. Dining (−$420.00) and travel (−$510.00) drove nearly all of it, while subscriptions crept up to $184.00 a month. Hold this pace and roughly $10,100.00 a year stays in your pocket — I can route that surplus to your Reserve automatically.",
    visual: { kind: "bars", title: "Monthly spend by category", bars: [{ label: "Dining", value: 1120, display: "-$420.00" }, { label: "Travel", value: 760, display: "-$510.00" }, { label: "Groceries", value: 690, display: "+$38.00" }], metrics: [["Total change", "-12.00%", "month over month"], ["Largest drop", "Travel", "-$510.00"], ["Watch", "Subscriptions", "$184.00"]] },
    detail: [
      "Dining: $700.00 across 14 charges, down from $1,120.00 in April — the pullback held all month, not just one week.",
      "Travel: $250.00 vs $760.00 in April — no flights this month; April carried the Denver trip.",
      "Groceries: $728.00, up $38.00 — right on your six-month average, nothing to chase.",
      "Subscriptions: 9 active totaling $184.00/mo. Netflix and Hulu overlap — dropping one saves about $186.00 a year.",
    ],
    ask: ["Route the savings to Reserve.", "If June holds like May, about $840.00 of monthly surplus is real. I can extend your cash-buffer rule to sweep it into Reserve at 3.30% APY, or point it at investing instead — which would you like drafted?"] },
];

Object.assign(window, {
  useState, useEffect, useRef, useMemo, useCallback,
  money, usd, signed, compactUsd, pct, perf, series, mulberry32,
  HOLDINGS, CASH, GROUPS, CASH_TOTAL, INVEST_TOTAL, CRYPTO_TOTAL, NET_WORTH, DAY_ABS, DAY_PCT,
  LIABILITIES, LIAB_TOTAL, ASSET_TOTAL, NET_WORTH_TRUE,
  NW_SERIES, NW_MARKERS, PROPOSALS, COMPLETED, ACTIVITY, CHAT_SEED, QUICK_REPLIES, THREADS, PROFILE,
  TRANSACTIONS, AUTOMATIONS, MARKET, TRENDING, EXPLORE_NEWS, MARKET_NEWS, HOLDINGS_NEWS, BRIEFS, BRIEF_INSIGHTS,
  sortTransactionsNewestFirst, transactionStamp, currentTransactionOccurrence,
  securityMarketQuote,
  MARKET_OPEN, INSTANT_FUNDING_CAP, INFLIGHT_RANK, COMING_UP_SEED,
});
