// ── Brief reading view ──────────────────────────────────────────────
// The morning desk note. Hero brief with a left rail of "Today's
// front page" — companies that produced material updates today.
/** When false, earnings release labels and earnings-based roster ordering are off. */
const BRIEF_SHOW_EARNINGS_RELEASE_INFO = false;
function _parseWindowParts(iso) {
if (!iso) return { weekday: "—", monShort: "—", day: "—", time: "—" };
const zone = _tzIana();
const d = new Date(iso);
return {
weekday: d.toLocaleDateString("en-US", { weekday: "short", timeZone: zone }),
monShort: d.toLocaleDateString("en-US", { month: "short", timeZone: zone }),
day: parseInt(d.toLocaleDateString("en-US", { day: "numeric", timeZone: zone }), 10),
time: d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", timeZone: zone, hour12: true }),
};
}
function _fmtDur(start, end) {
if (!start || !end) return "—";
const ms = new Date(end) - new Date(start);
if (ms < 0) return "—";
const mins = Math.round(ms / 60000);
const h = Math.floor(mins / 60);
const m = mins % 60;
if (h < 1) return `${m}m`;
return m > 0 ? `${h}h ${m}m` : `${h}h`;
}
function BriefWindowBand({ start, end }) {
const s = _parseWindowParts(start);
const e = _parseWindowParts(end);
return (
{s.weekday}, {s.monShort} {s.day}
{s.time}
{_fmtDur(start, end)}
▸
{e.weekday}, {e.monShort} {e.day}
{e.time}
);
}
function BriefView({ density, showDiscarded, dropcap, setShowDiscarded, setView, view, briefLayout, setBriefLayout, appPortfolioIds, appLandingSummaries, appEvents }) {
const initialBrief = window.DATA.todaysBrief;
const initialDates = window.DATA.availableDates || [];
const initialDate = initialBrief?.windowEnd?.slice(0, 10) || initialDates[initialDates.length - 1] || null;
const [currentBrief, setCurrentBrief] = React.useState(null);
// mode derived from view: "overview" → brief, "overview-audit" → audit, "overview-archive" → archive
const mode = view === "overview-audit" ? "audit" : view === "overview-archive" ? "archive" : "brief";
const setMode = (m) => setView(m === "audit" ? "overview-audit" : m === "archive" ? "overview-archive" : "overview");
const [currentPulse, setCurrentPulse] = React.useState(window.DATA.pulse);
const [availableDates, setAvailableDates] = React.useState(initialDates);
const [selectedDate, setSelectedDate] = React.useState(initialDate);
const [companySummaries, setCompanySummaries] = React.useState(window.DATA.companySummaries || {});
const landingSummaries = appLandingSummaries || window.DATA.companySummaries || {};
const _summariesDateRef = React.useRef(initialDate); // tracks which date companySummaries currently reflects
const [loading, setLoading] = React.useState(false);
const [activeBulletId, setActiveBulletId] = React.useState(null);
const [filterTheme, setFilterTheme] = React.useState(null);
const [relatedBriefs, setRelatedBriefs] = React.useState([]);
const [companySearch, setCompanySearch] = React.useState("");
const [entitySignals, setEntitySignals] = React.useState(null);
const [signalsLoading, setSignalsLoading] = React.useState(false);
const [signalMode, setSignalMode] = React.useState("zscore"); // "zscore" | "raw"
const portfolioIds = appPortfolioIds; // null = loading, Set = loaded
const _loadingEntityRef = React.useRef(null); // prevents auto-load from racing with explicit loadEntity calls
// Reset to landing when The Brief nav is clicked
React.useEffect(() => {
if (view === "brief") {
setCurrentBrief(null);
setCurrentPulse(window.DATA.pulse || []);
}
}, [view]);
const brief = currentBrief;
React.useEffect(() => {
const entityId = brief?.entityId;
const date =
selectedDate ||
(brief?.windowEnd && brief.windowEnd.slice(0, 10)) ||
null;
if (!entityId || !date) {
setRelatedBriefs([]);
return;
}
let cancelled = false;
fetch(
`/api/frontend/brief/related?entity_id=${encodeURIComponent(entityId)}&date=${encodeURIComponent(date)}`
)
.then(r => r.json())
.then(data => {
if (cancelled) return;
const list = Array.isArray(data.related) ? data.related : [];
const shuffled = [...list];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const t = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = t;
}
setRelatedBriefs(shuffled.slice(0, 3));
})
.catch(() => { if (!cancelled) setRelatedBriefs([]); });
return () => { cancelled = true; };
}, [brief?.entityId, brief?.windowEnd, selectedDate]);
const _allCompanies = Array.isArray(window.DATA?.companies) ? window.DATA.companies : [];
const allCompanies = portfolioIds === null
? []
: portfolioIds.size === 0
? _allCompanies
: _allCompanies.filter(c => portfolioIds.has(c.id));
// Auto-load top company when navigating to any overview* view with no brief loaded.
// Skips if loadEntity is already in flight (race-condition guard via _loadingEntityRef).
React.useEffect(() => {
const isOverview = view === "overview" || view === "overview-audit" || view === "overview-archive";
if (!isOverview || currentBrief || !portfolioIds || _loadingEntityRef.current) return;
const top = [...allCompanies]
.filter(c => companySummaries[c.id]?.bulletsSaved > 0)
.sort((a, b) => (companySummaries[b.id]?.bulletsSaved ?? 0) - (companySummaries[a.id]?.bulletsSaved ?? 0))[0];
if (top) loadEntity(top.id, selectedDate, view);
}, [view, portfolioIds]);
const _searchLower = companySearch.toLowerCase();
const _filterCompany = (c) => !_searchLower ||
c.name.toLowerCase().includes(_searchLower) ||
(c.ticker || "").toLowerCase().includes(_searchLower);
// Landing picker: sorted by bullet count descending
const landingCompanies = React.useMemo(() =>
[...allCompanies].sort((a, b) =>
(landingSummaries[b.id]?.bulletsSaved ?? 0) - (landingSummaries[a.id]?.bulletsSaved ?? 0)
),
[landingSummaries, allCompanies]);
// Overview left rail: filtered to companies that ran on selectedDate, sorted by bullet count
const companiesForFrontPage = React.useMemo(() => {
const bid = brief?.entityId;
const list = allCompanies.filter(c => {
if (c.id === bid) return true;
const s = companySummaries[c.id];
if (!s) return false;
if (s.hasRunOnDate === true) return true;
if (s.hasRunOnDate === false) return false;
return (s.bulletsSaved ?? 0) > 0;
});
return [...list].sort((a, b) =>
(companySummaries[b.id]?.bulletsSaved ?? 0) - (companySummaries[a.id]?.bulletsSaved ?? 0)
);
}, [companySummaries, brief?.entityId, allCompanies]);
function refreshSidebar(date) {
const url = `/api/frontend/companies/summaries` + (date ? `?date=${date}` : "");
fetch(url)
.then(r => r.json())
.then(data => {
if (data.summaries) {
setCompanySummaries(data.summaries);
_summariesDateRef.current = date;
}
})
.catch(console.error);
}
React.useEffect(() => {
if (selectedDate && selectedDate !== _summariesDateRef.current) refreshSidebar(selectedDate);
}, [selectedDate]);
React.useEffect(() => {
const entityId = brief?.entityId;
if (!entityId) {
setEntitySignals(null);
return;
}
let cancelled = false;
setSignalsLoading(true);
const endParam = selectedDate ? `&end_date=${encodeURIComponent(selectedDate)}` : "";
fetch(`/api/frontend/entity/${encodeURIComponent(entityId)}/signals?days=30${endParam}`)
.then(r => r.json())
.then(data => { if (!cancelled) setEntitySignals(data); })
.catch(console.error)
.finally(() => { if (!cancelled) setSignalsLoading(false); });
return () => { cancelled = true; };
}, [brief?.entityId, selectedDate]);
function loadEntity(entityId, date, targetView = "overview") {
const targetDate =
typeof date === "string" && /^\d{4}-\d{2}-\d{2}/.test(date) ? date.slice(0, 10) : null;
_loadingEntityRef.current = entityId;
setView(targetView);
setLoading(true);
setFilterTheme(null);
setActiveBulletId(null);
const url = `/api/frontend/entity/${entityId}/brief` + (targetDate ? `?date=${encodeURIComponent(targetDate)}` : "");
fetch(url)
.then(r => r.json())
.then(data => {
if (data.availableDates) setAvailableDates(data.availableDates);
if (data.brief) {
setCurrentBrief(data.brief);
setCurrentPulse(data.pulse || []);
// Keep sidebar counts in sync: brief already aggregates all day's runs
setCompanySummaries(prev => ({
...prev,
[entityId]: {
...(prev[entityId] || {}),
bulletsSaved: data.brief.bulletsSaved,
bulletsDiscarded: data.brief.bulletsDiscarded,
lastRunDate: data.brief.coverageEnd || data.brief.windowEnd,
},
}));
const asked = targetDate && /^\d{4}-\d{2}-\d{2}$/.test(targetDate) ? targetDate.slice(0, 10) : null;
if (asked) {
setSelectedDate(asked);
} else {
const rd = (data.selectedDate || data.brief.windowEnd || "").toString().slice(0, 10);
if (/^\d{4}-\d{2}-\d{2}$/.test(rd)) setSelectedDate(rd);
}
}
})
.catch(console.error)
.finally(() => { setLoading(false); _loadingEntityRef.current = null; });
}
function navigateDate(direction) {
const key = (selectedDate || brief?.windowEnd || "").toString().slice(0, 10);
const idx = availableDates.indexOf(key);
const nextIdx = idx + direction;
if (nextIdx < 0 || nextIdx >= availableDates.length) return;
const newDate = availableDates[nextIdx];
setSelectedDate(newDate);
loadEntity(brief?.entityId, newDate, view);
}
const navDateKey = (selectedDate || brief?.windowEnd || "").toString().slice(0, 10);
const dateIdx = availableDates.indexOf(navDateKey);
const canPrev = dateIdx > 0;
const canNext = dateIdx >= 0 && dateIdx < availableDates.length - 1;
if (view === "brief") {
return ;
}
// view === "overview"
if (!brief || !Array.isArray(brief.bullets)) {
return (
{loading ? "Loading…" : "No company data available."}
);
}
const allBullets = brief.bullets;
const themesList = Array.isArray(brief.themes) ? brief.themes : [];
const discardedList = Array.isArray(brief.discarded) ? brief.discarded : [];
// Assign maximally-separated hues using golden angle so sibling themes never clash
const _dark = document.documentElement.dataset.theme === "dark";
const _themeColorList = themesList.map((_, i) =>
`hsl(${(i * 137.508 + 30) % 360}, 70%, ${_dark ? 62 : 38}%)`
);
const themeColors = {};
themesList.forEach((t, i) => { themeColors[t.name] = _themeColorList[i]; });
const bullets = filterTheme
? allBullets.filter(b => b.theme === filterTheme)
: allBullets;
const novelCount = allBullets.filter(b => b.novelty === "novel").length;
const rewrittenCount = allBullets.filter(b => b.novelty === "rewritten").length;
return (
{/* ── Left rail: today's front page ── */}
{/* ── Main column ── */}
{/* Date navigation — shown in brief, audit and archive (buttons hidden in archive) */}
{(mode === "brief" || mode === "audit" || mode === "archive") && (
{selectedDate ? (() => {
const [y, m, d] = selectedDate.split("-").map(Number);
return new Date(Date.UTC(y, m - 1, d)).toLocaleDateString("en-US", { weekday: "long", day: "numeric", month: "long", year: "numeric", timeZone: "UTC" });
})() : "—"}
navigateDate(-1)} disabled={!canPrev || loading}
style={{ fontFamily: "var(--mono)", fontSize: 12, padding: "3px 8px", border: "1px solid var(--rule)", background: "var(--paper)", color: canPrev ? "var(--ink)" : "var(--ink-faint)", cursor: canPrev ? "pointer" : "default", opacity: canPrev ? 1 : 0.4, visibility: mode === "archive" ? "hidden" : "visible" }}
title="Previous day">← prev
navigateDate(1)} disabled={!canNext || loading}
style={{ fontFamily: "var(--mono)", fontSize: 12, padding: "3px 8px", border: "1px solid var(--rule)", background: "var(--paper)", color: canNext ? "var(--ink)" : "var(--ink-faint)", cursor: canNext ? "pointer" : "default", opacity: canNext ? 1 : 0.4, visibility: mode === "archive" ? "hidden" : "visible" }}
title="Next day">next →
{loading && loading… }
)}
{mode === "archive" && (
{ setMode("brief"); loadEntity(brief.entityId, d); }} />
)}
{mode === "audit" && (
<>
{brief?.entityName} — Audit
Every bullet, kept or cut for {brief?.ticker || brief?.entityName}
>
)}
{mode === "brief" && (<>
{/* Hero */}
{brief?.entityName}
{brief?.ticker ? `What's new on ` : `What's new at `}{brief?.ticker || brief?.entityName} this morning.
{loading
? Loading…
: brief?.noRunForWindow
?
No pipeline run for this calendar day. Use prev/next to move along the coverage range, or run the scanner for this window.
: brief?.bulletsSaved === 0
? No material developments for this period.
: brief?.narrative
? {brief.narrative}
: Narrative not available.
}
{brief.bulletsSaved}
Material developments
{brief.sourcesScanned}
Available sources
{brief.chunksReviewed}
Excerpts reviewed
{brief.bulletsDiscarded}
Filtered out
{Math.round(brief.durationSec / 60)}m
Pipeline runtime
{!brief.noRunForWindow && (brief.coverageStart || brief.windowStart) && (
)}
{/* Theme filter row */}
Sections
setFilterTheme(null)}>
All {allBullets.length}
{themesList.map(t => (
setFilterTheme(t.name)}>
{t.name} {t.count}
))}
{/* Bullets, grouped by theme when no filter */}
{bullets.map((b, i) => (
setActiveBulletId(activeBulletId === b.id ? null : b.id)}
themeColor={themeColors[b.theme]}
/>
))}
>)}
{/* ── Right rail: meta ── */}
About this brief
{brief.entityName}
{_tk(brief.ticker)}
{brief.exchange && · {brief.exchange} }
{brief.sector && <>Sector {brief.sector} >}
{brief.industry && <>Industry {brief.industry} >}
{brief.country && <>Country {brief.country} >}
Entity ID {brief.entityId}
{brief.webpage && <>Web {brief.webpage.replace(/^https?:\/\//, "").replace(/\/$/, "")} >}
{(() => {
// Build a fixed 14-day window ending on selectedDate, filling missing days with 0
const _endIso = selectedDate || brief?.windowEnd?.slice(0, 10) || new Date().toISOString().slice(0, 10);
const _14dates = Array.from({ length: 14 }, (_, i) => {
const d = new Date(_endIso + "T00:00:00Z");
d.setUTCDate(d.getUTCDate() - (13 - i));
return d.toISOString().slice(0, 10);
});
const _pulseByDate = Object.fromEntries(currentPulse.map(p => [p.date?.slice(0, 10), p.saved ?? 0]));
const pulseValues = _14dates.map(d => _pulseByDate[d] ?? 0);
const avg = pulseValues.reduce((a, b) => a + b, 0) / 14;
const selectedValue = pulseValues[13]; // last = end date
const firstDate = _14dates[0].slice(5);
const lastDate = _14dates[13].slice(5);
return (
<>
14-day pulse
Material developments per day
{firstDate}
{lastDate}
Peak
{Math.max(...pulseValues)}
>
);
})()}
{/* Signal history */}
Signal history
{signalsLoading ? (
Loading…
) : entitySignals && entitySignals.signals && entitySignals.signals.length > 0 ? (() => {
const sigs = entitySignals.signals;
const last = sigs[sigs.length - 1];
const chunksVals = sigs.map(s => s.chunks_zscore_mo ?? 0);
const sentVals = sigs.map(s => s.sent_ewm_short ?? 0);
const firstDate = sigs[0]?.date?.slice(5) || "";
const lastDate = last?.date?.slice(5) || "";
const _fmtN = (v, dec = 4) => v == null ? "—" : (v >= 0 ? "+" : "") + v.toFixed(dec);
const _fmtZ = (v, dec = 1) => v == null ? "—" : (v >= 0 ? "+" : "") + v.toFixed(dec);
const _interpZ = (v) => {
if (v == null) return "";
const az = Math.abs(v), z = _fmtZ(v);
if (az <= 1.0) return `Normal range (z=${z})`;
if (az <= 2.0) return `${v < 0 ? "Below" : "Above"} average (z=${z})`;
return `Well ${v < 0 ? "below" : "above"} average (z=${z})`;
};
const _interpMomSent = (v) => {
if (v == null) return "";
const f = v.toFixed(1), s = v >= 0 ? "+" : "";
if (Math.abs(v) < 0.02) return `─ Stable (${s}${f})`;
return v > 0 ? `↑ Rising (${s}${f})` : `↓ Falling (${s}${f})`;
};
const _interpMomPct = (v) => {
if (v == null) return "";
const f = v.toFixed(1), s = v >= 0 ? "+" : "";
if (Math.abs(v) < 5) return `─ Stable (${s}${f}%)`;
return v > 0 ? `↑ Rising (${s}${f}%)` : `↓ Falling (${s}${f}%)`;
};
const MetricRow = ({ label, value, interp, color }) => (
{label}
{value}
{interp && {interp} }
);
const _col = (v) => v == null ? "inherit" : v >= 0 ? "var(--novel)" : "var(--discard)";
return (
{/* Media attention sparkline */}
Media attention
{firstDate} {lastDate}
= 0 ? "+" : "") + last.chunks_momentum_pct.toFixed(1) + "%" : "—"} interp={_interpMomPct(last.chunks_momentum_pct)} color={_col(last.chunks_momentum_pct)} />
{/* Sentiment sparkline — diverging area with min/max gutter */}
Sentiment
{(() => {
const sMin = Math.min(...sentVals);
const sMax = Math.max(...sentVals);
const sNow = sentVals[sentVals.length - 1];
const sgn = (v) => v >= 0 ? "+" : "−";
const num = (v, d = 3) => v == null ? "—" : sgn(v) + Math.abs(v).toFixed(d);
return (
min = 0 ? "var(--novel)" : "var(--discard)" }}>{num(sMin)}
now = 0 ? "var(--novel)" : "var(--discard)" }}>{num(sNow)}
max = 0 ? "var(--novel)" : "var(--discard)" }}>{num(sMax)}
);
})()}
{firstDate} {lastDate}
);
})() : (
No signal data available.
)}
{/* Audit link removed — Audit is now an inline tab at top of brief */}
{relatedBriefs.length > 0 && (
)}
);
}
// ── Single bullet ───────────────────────────────────────────────────
function BulletItem({ bullet, index, isFirst, active, onActivate, themeColor }) {
const [noteOpen, setNoteOpen] = React.useState(false);
const [expandedSources, setExpandedSources] = React.useState(new Set());
React.useEffect(() => {
if (!active) setExpandedSources(new Set());
}, [active]);
const toggleSource = (src) => {
setExpandedSources(prev => {
const next = new Set(prev);
if (next.has(src)) next.delete(src); else next.add(src);
return next;
});
};
const groupedCitations = _groupCitations(bullet.citations);
return (
{String(index + 1).padStart(2, "0")}
{bullet.theme}
{bullet.text}
{bullet.citations.map((c, i) => )}
{bullet.novelty === "rewritten" && (
setNoteOpen(o => !o)}>
Editor's note
{noteOpen ? "▴" : "▾"}
{noteOpen && {bullet.rewriteReason} }
)}
{groupedCitations.map((sg, gi) => {
const isOpen = active || expandedSources.has(sg.source);
const excerptCount = sg.headlineGroups.reduce((s, hg) => s + hg.excerpts.length, 0);
return (
toggleSource(sg.source)}
>
{sg.source}{excerptCount > 1 && ({excerptCount}) }
);
})}
{active ? "Hide" : "View all"}
{(active || expandedSources.size > 0) && (
{groupedCitations
.filter(sg => active || expandedSources.has(sg.source))
.map((sg, si) => (
{sg.source}{sg.date ? · {sg.date} : null}
{sg.headlineGroups.map((hg, hi) => (
{hg.headline}
{hg.excerpts.length === 1
?
"{hg.excerpts[0]}"
: hg.excerpts.map((ex, xi) => (
))
}
))}
))}
)}
);
}
// ── Discarded list ──────────────────────────────────────────────────
function DiscardedList({ items }) {
const grouped = items.reduce((acc, item) => {
if (!acc[item.stage]) acc[item.stage] = [];
acc[item.stage].push(item);
return acc;
}, {});
const stageLabels = {
relevance_score: "Relevance score",
grounding: "Grounding",
novelty_embedding: "Novelty (embedding)",
novelty_search: "Novelty (search)",
};
return (
Items the pipeline considered and rejected, with the reason. Helps audit what the brief did not tell you.
{Object.entries(grouped).map(([stage, list]) => (
{stageLabels[stage] || stage}
{list.length} item{list.length > 1 ? "s" : ""}
{list.map(item => (
{item.text}
— {item.reason}
))}
))}
);
}
window.BriefView = BriefView;
// ── Archive bullet item — bullet text + collapsible source chips ────
function ArchiveBulletItem({ bullet, index }) {
const [expandedSources, setExpandedSources] = React.useState(new Set());
const groupedCitations = _groupCitations(bullet.citations || []);
const toggleSource = (src) => setExpandedSources(prev => {
const next = new Set(prev);
if (next.has(src)) next.delete(src); else next.add(src);
return next;
});
return (
{bullet.theme && (
{bullet.theme}
)}
{bullet.text}
{groupedCitations.length > 0 && (
{groupedCitations.map((sg, gi) => {
const isOpen = expandedSources.has(sg.source);
const excerptCount = sg.headlineGroups.reduce((s, hg) => s + hg.excerpts.length, 0);
return (
toggleSource(sg.source)}
>
{sg.source}{excerptCount > 1 && ({excerptCount}) }
);
})}
)}
{expandedSources.size > 0 && (
{groupedCitations.filter(sg => expandedSources.has(sg.source)).map((sg, si) => (
{sg.source}{sg.date ? · {sg.date} : null}
{sg.headlineGroups.map((hg, hi) => (
{hg.headline}
{hg.excerpts.length === 1
?
"{hg.excerpts[0]}"
: hg.excerpts.map((ex, xi) => (
))
}
))}
))}
)}
);
}
// ── Brief landing ─────────────────────────────────────────
// Two-column split: Portfolio Brief narrative on the left, company picker on the right.
function BriefLanding({ loading, companies, allCompanies, summaries, onPick, companySearch, setCompanySearch, selectedDate, briefLayout, setBriefLayout, upcomingEvents, eventsLoading }) {
const [pbView, setPbView] = React.useState("bullets"); // "bullets" | "summary"
// Stats are always computed from the full (unfiltered) company list for the selected date
const _statsBase = (allCompanies || companies).filter(c => summaries[c.id]?.hasRunOnDate === true);
const ranToday = _statsBase;
const totalSaved = ranToday.reduce((s, c) => s + (summaries[c.id]?.bulletsSaved || 0), 0);
const totalDiscarded = ranToday.reduce((s, c) => s + (summaries[c.id]?.bulletsDiscarded || 0), 0);
const moversAll = ranToday
.map(c => ({ ...c, saved: summaries[c.id]?.bulletsSaved || 0, discarded: summaries[c.id]?.bulletsDiscarded || 0 }))
.filter(c => c.saved > 0)
.sort((a, b) => b.saved - a.saved);
const movers = moversAll.slice(0, 5); // top 5 for display list
const activeCount = moversAll.length; // actual count of active companies
// Portfolio brief state
const [portfolioBrief, setPortfolioBrief] = React.useState(null);
const [briefLoading, setBriefLoading] = React.useState(false);
const [narrativeMode, setNarrativeMode] = React.useState("thematic"); // "thematic" | "lead"
const dateLabel = React.useMemo(() => {
const iso = portfolioBrief?.date;
if (!iso) return null;
const [y, m, d] = iso.split("-").map(Number);
return new Date(Date.UTC(y, m - 1, d)).toLocaleDateString("en-US", {
weekday: "long", month: "long", day: "numeric", year: "numeric", timeZone: "UTC",
});
}, [portfolioBrief?.date]);
// Fetch portfolio brief — always uses most recent date (no date param)
React.useEffect(() => {
setBriefLoading(true);
fetch("/api/frontend/portfolio-brief?top_n=5")
.then(r => r.json())
.then(data => setPortfolioBrief(data))
.catch(() => setPortfolioBrief(null))
.finally(() => setBriefLoading(false));
}, []);
function _fmtEventDateTime(iso) {
if (!iso) return { date: "—", time: "" };
const d = new Date(iso);
const zone = _tzIana();
const date = d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", timeZone: zone });
const time = d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true, timeZone: zone });
return { date, time };
}
const [showExtraCols, setShowExtraCols] = React.useState(false);
const leftRef = React.useRef(null);
const pickListRef = React.useRef(null);
const _layout = briefLayout || "below";
// Cap pick-list height to match left panel in both layouts
React.useLayoutEffect(() => {
const list = pickListRef.current;
const left = leftRef.current;
if (!list || !left) return;
const leftH = left.getBoundingClientRect().height;
const header = list.previousElementSibling;
const headerH = header ? header.getBoundingClientRect().height : 0;
const available = Math.max(leftH - headerH, 220);
list.style.maxHeight = available + "px";
list.style.overflowY = "auto";
}, [_layout, companies.length, portfolioBrief?.date, briefLoading, upcomingEvents]);
const narrativeText = narrativeMode === "lead" && portfolioBrief?.narrative_b
? portfolioBrief.narrative_b
: portfolioBrief?.narrative;
const companiesCount = ranToday.length;
const events = upcomingEvents?.events || [];
// Group events by day for calendar strip
const eventsByDay = React.useMemo(() => {
const zone = _tzIana();
const days = {};
events.forEach(ev => {
const d = new Date(ev.event_datetime);
const key = d.toLocaleDateString("en-US", { year: "numeric", month: "2-digit", day: "2-digit", timeZone: zone });
if (!days[key]) days[key] = { date: d, events: [] };
days[key].events.push(ev);
});
return Object.values(days).sort((a, b) => a.date - b.date);
}, [events]);
const eventsInPanelBlock = (
<>
Next closest events
{eventsLoading ? (
Loading events…
) : events.length === 0 ? (
No upcoming events found.
) : (
{events.map((ev, i) => {
const { date: evDate, time: evTime } = _fmtEventDateTime(ev.event_datetime);
return (
{evDate}
{evTime}
{DISPLAY_TZ}
{ev.entity_name}{ev.fiscal_period ? ` · ${ev.fiscal_period}` : ""}{ev.fiscal_year ? ` ${ev.fiscal_year}` : ""}
{ev.category === "conference-call" ? "Conference" : "Earnings Call"}
);
})}
)}
>
);
const eventsBelowBlock = eventsByDay.length > 0 && (
Next closest events
{eventsByDay.map((day, di) => {
const zone = _tzIana();
const dow = day.date.toLocaleDateString("en-US", { weekday: "short", timeZone: zone });
const num = day.date.toLocaleDateString("en-US", { day: "numeric", timeZone: zone });
const mon = day.date.toLocaleDateString("en-US", { month: "short", timeZone: zone });
return (
{dow}
{num}
{mon}
{day.events.map((ev, ei) => {
const time = new Date(ev.event_datetime).toLocaleTimeString("en-US", {
hour: "numeric", minute: "2-digit", hour12: true, timeZone: zone,
});
return (
{time} {DISPLAY_TZ}
{ev.entity_name}
{ev.category === "conference-call" ? "Conference" : "Earnings Call"}
{(ev.fiscal_period || ev.fiscal_year) && (
{[ev.fiscal_period, ev.fiscal_year].filter(Boolean).join(" ")}
)}
);
})}
);
})}
);
return (
{/* LEFT: Company picker */}
{loading ? "Loading…" : companies.length === 0 ? "Add companies in My Portfolio to see briefs here." : "Choose a company to read its brief."}
setCompanySearch(e.target.value)}
style={{ marginTop: 12 }}
/>
Ticker
Company
Items
{companies.map(c => {
const s = summaries[c.id] || {};
const saved = s.bulletsSaved != null ? s.bulletsSaved : "—";
return (
onPick(c.id, null)} disabled={loading}>
{_tk(c.ticker)}
{c.name}
{saved}
);
})}
{/* RIGHT: Portfolio Brief */}
Portfolio Brief{dateLabel ? ` — ${dateLabel}` : ""}
setPbView("bullets")}>Bullet Points
setPbView("summary")}>Summary
Where the news moved.
The most active names in your portfolio today — short reads, one per company.
{companiesCount} companies
{totalSaved} material developments
{activeCount} active names
{briefLoading
? null
: pbView === "summary"
? (narrativeText
? (() => {
const _companyById = {};
(portfolioBrief?.companies || []).forEach(c => { _companyById[c.name] = c.entityId; });
return narrativeText.split("\n\n").map((section, i) => {
const nl = section.indexOf("\n");
const company = nl === -1 ? section : section.slice(0, nl);
const body = nl === -1 ? "" : section.slice(nl + 1);
const entityId = _companyById[company];
const _go = entityId ? () => onPick(entityId, null) : null;
return (
{company}
{body &&
{body}
}
);
});
})()
:
No portfolio brief available yet — will be generated after the next run.
)
: ((portfolioBrief?.bullets_by_company || []).length > 0
? (portfolioBrief.bullets_by_company).map((c, i) => {
const _go = c.entityId ? () => onPick(c.entityId, null) : null;
return (
{c.name}
{c.bullets.map((b, j) => {b} )}
);
})
:
No bullet points available yet.
)
}
{_layout === "in-panel" && eventsInPanelBlock}
{_layout === "below" && eventsBelowBlock}
);
}
// ── Inline Archive for the selected entity (Change 4) ───────────────
function BriefEntityArchive({ entityId, entityName, ticker, onOpenDate }) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [expandedRunId, setExpandedRunId] = React.useState(null);
React.useEffect(() => {
setLoading(true);
fetch(`/api/frontend/entity/${entityId}/history`)
.then(r => r.json())
.then(d => setData(d))
.catch(console.error)
.finally(() => setLoading(false));
}, [entityId]);
if (loading || !data) {
return Loading archive…
;
}
const history = data.history || [];
return (
{entityName} — Archive
Every brief filed for {ticker || entityName}
{history.length} runs · {history.reduce((s, h) => s + h.saved, 0)} bullets saved · {history.reduce((s, h) => s + h.discarded, 0)} discarded
{history.map(entry => {
const d = new Date(entry.date + "T00:00:00Z");
const day = d.getUTCDate();
const month3 = d.toLocaleDateString("en-US", { month: "short", timeZone: "UTC" });
const wd = d.toLocaleDateString("en-US", { weekday: "short", timeZone: "UTC" });
return (
{String(day).padStart(2, "0")}
{month3}
{wd}
onOpenDate(entry.date)}>
{entry.narrative || entry.bullets?.[0]?.text || "No material developments"}
run-{entry.runId}
·
{entry.saved} saved
·
{entry.discarded} discarded
{entry.bullets?.length > 0 && (
setExpandedRunId(expandedRunId === entry.runId ? null : entry.runId)}>
{expandedRunId === entry.runId ? "▴ hide bullets" : `▾ ${entry.bullets.length} bullet${entry.bullets.length !== 1 ? "s" : ""}`}
)}
{expandedRunId === entry.runId && entry.bullets?.length > 0 && (
{entry.bullets.map((b, i) => (
))}
)}
);
})}
{history.length === 0 && (
No briefs found for this entity.
)}
);
}
// ── Inline Audit (forensic) for the selected entity (Change 4) ──────
// ── Inline Audit — renders identical content to HistoryDetailsView (no sidebar) ──
function BriefEntityAudit({ entityId, selectedDate }) {
const [forensicsData, setForensicsData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [openRunId, setOpenRunId] = React.useState(null);
const [expandedRejection, setExpandedRejection] = React.useState(null);
const [expandedPubCitation, setExpandedPubCitation] = React.useState(null);
React.useEffect(() => {
if (!entityId) return;
setLoading(true);
fetch(`/api/frontend/entity/${entityId}/forensics`)
.then(r => r.json())
.then(d => {
setForensicsData(d);
// Auto-open the run matching selectedDate, else open the first run
if (d.days && d.days.length > 0) {
const targetDay = selectedDate
? d.days.find(day => day.date === selectedDate)
: d.days[0];
const day = targetDay || d.days[0];
if (day && day.runs && day.runs.length > 0) {
setOpenRunId(day.runs[0].runId);
}
}
})
.catch(console.error)
.finally(() => setLoading(false));
}, [entityId, selectedDate]);
if (loading) return (
Loading audit…
);
const allDays = forensicsData?.days || [];
// Show only the day matching the selected date
const days = selectedDate
? allDays.filter(d => d.date === selectedDate)
: allDays.slice(0, 1);
if (!days.length) return (
No audit data for this date.
);
return (
{days.map(d => {
const isMulti = d.runs.length > 1;
// Static header — no collapse, always expanded
const RunHeader = ({ r }) => (
{r.published} published
·
{r.rejected} rejected
·
run-{r.runId}
{r.windowStart && (
{_fmtWindow(r.windowStart, r.windowEnd)}
)}
);
if (!isMulti) {
const r = d.runs[0];
return (
);
}
const totalPub = d.runs.reduce((s, r) => s + r.published, 0);
const totalRej = d.runs.reduce((s, r) => s + r.rejected, 0);
return (
{d.runs.length} runs · {totalPub} published · {totalRej} rejected
{d.runs.map(r => (
))}
);
})}
);
}