/** * Upstream Command Center — React SPA * Hash-based routing, no react-router required. * * Pages: Overview, Dashboard, Intelligence, Batch Review, Ads, Pipeline, * Newsletter, Digest, Analytics, Activity Log, + Sales/Contact placeholders * * Security note: Blog tab renders pipeline-generated HTML. DOMPurify.sanitize() * is applied before any DOM mutation (defense-in-depth per PREV-04 spec). * * Sales page components (OverviewPage, AnalyticsPage, ActivityLogPage, etc.) * are defined in sales_app.jsx which loads before this file. */ const { useState, useEffect, useCallback, useRef } = React; // --------------------------------------------------------------------------- // API base URL — empty string for same-origin (local dev), set for Cloud Run // --------------------------------------------------------------------------- const API_BASE = window.__UPSTREAM_API_BASE || ""; function apiFetch(path, options = {}) { return fetch(API_BASE + path, { ...options, credentials: "include", headers: { ...options.headers, "Content-Type": "application/json" }, }); } // --------------------------------------------------------------------------- // Shared components // --------------------------------------------------------------------------- function Badge({ status }) { const cls = { active: "badge badge-ok", idle: "badge badge-pending", ok: "badge badge-ok", error: "badge badge-error", }[status] || "badge"; return React.createElement("span", { className: cls }, status); } function QualityBadge({ score }) { if (score == null) return null; const n = parseFloat(score); const cls = n >= 8 ? "badge badge-ok" : n >= 6 ? "badge badge-pending" : "badge badge-error"; return React.createElement("span", { className: cls }, n.toFixed(1)); } function PillarBadge({ pillar }) { const label = (pillar || "").replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()); return React.createElement("span", { className: "pillar-badge" }, label); } function CopyButton({ text }) { const [copied, setCopied] = useState(false); function handleCopy() { navigator.clipboard.writeText(text || "").then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); } return ( ); } function Table({ columns, rows, emptyMessage }) { if (!rows || rows.length === 0) { return
{emptyMessage || "No data"}
; } return ( {columns.map(c => )} {rows.map((row, i) => ( {columns.map(c => ( ))} ))}
{c.label}
{c.render ? c.render(row) : row[c.key]}
); } // --------------------------------------------------------------------------- // Dashboard page // --------------------------------------------------------------------------- function TrackCard({ track }) { const accentColors = { "Intelligence": "#2D7DD2", "Digest": "#4CAF7D", "Blog": "#9B59B6", }; const accent = accentColors[track.name] || "#2D7DD2"; return (
{track.name}
{track.brief_count != null && (
Briefs {track.brief_count}
)} {track.articles_published != null && (
Published {track.articles_published}
)} {track.articles_pending != null && (
Pending {track.articles_pending}
)}
{track.last_run ? `Last run: ${track.last_run}` : "No runs yet"}
); } function DashboardPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { apiFetch("/api/dashboard") .then(r => r.json()) .then(d => { setData(d); setLoading(false); }) .catch(e => { setError(e.message); setLoading(false); }); }, []); if (loading) return
Loading dashboard...
; if (error) return
Error: {error}
; const { tracks = [], pipeline_health = [], analytics = {} } = data; const healthColumns = [ { key: "module", label: "Module" }, { key: "last_success", label: "Last Success", render: r => r.last_success || "—" }, { key: "next_scheduled", label: "Next Scheduled" }, { key: "status", label: "Status", render: r => }, ]; return (

Dashboard

Pipeline health and 30-day activity at a glance

{tracks.map(track => )}

Pipeline Health

Monthly Cost
${(analytics.total_cost_month || 0).toFixed(2)}
This month (USD)
Articles Published
{analytics.articles_this_month || 0}
This month
Social Posts Queued
{analytics.social_posts_queued || 0}
Typefully queue
); } // --------------------------------------------------------------------------- // Intelligence page // --------------------------------------------------------------------------- function IntelligencePage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [sortField, setSortField] = useState("novelty_score"); const [sortDir, setSortDir] = useState("desc"); useEffect(() => { apiFetch("/api/intelligence") .then(r => r.json()) .then(d => { setData(d); setLoading(false); }) .catch(e => { setError(e.message); setLoading(false); }); }, []); if (loading) return
Loading intelligence brief...
; if (error) return
Error: {error}
; const { brief = {}, signals = [], topic_ideas = [] } = data; const sortedSignals = [...signals].sort((a, b) => { const av = a[sortField] ?? 0; const bv = b[sortField] ?? 0; return sortDir === "desc" ? bv - av : av - bv; }); function toggleSort(field) { if (sortField === field) { setSortDir(d => d === "desc" ? "asc" : "desc"); } else { setSortField(field); setSortDir("desc"); } } const sortIcon = (field) => sortField === field ? (sortDir === "desc" ? " ↓" : " ↑") : ""; return (

Intelligence

Latest weekly brief, signal table, and topic ideas

{brief && (brief.community_pulse || brief.policy_and_industry || brief.connecting_thread) && (

Weekly Brief

{brief.week_id &&
Week: {brief.week_id}
}
{brief.community_pulse && (
Community Pulse
{brief.community_pulse}
)} {brief.policy_and_industry && (
Policy and Industry
{brief.policy_and_industry}
)} {brief.connecting_thread && (
Connecting Thread
{brief.connecting_thread}
)}
)}

Signals ({signals.length})

{signals.length === 0 ? (
No signals yet — run uv run pipeline/run.py monitor
) : (
{sortedSignals.map((s, i) => ( ))}
Source Title Category toggleSort("novelty_score")}> Novelty{sortIcon("novelty_score")}
{s.source || "—"} {s.title || s.headline || "—"} {s.category || s.pillar || "—"}
)}
{topic_ideas && topic_ideas.length > 0 && (

Topic Suggestions ({topic_ideas.length})

{topic_ideas.map((t, i) => (
{t.title || t.topic || "Untitled"}
{t.pillar && } {t.seo_angle &&
{t.seo_angle}
} {t.novelty_score != null && (
Novelty:
)}
))}
)}
); } // --------------------------------------------------------------------------- // Batch page — master-detail with 8-tab article preview // --------------------------------------------------------------------------- const TAB_LABELS = [ { id: "blog", label: "Blog" }, { id: "intelligence_edition", label: "Intelligence Edition" }, { id: "executive_briefing", label: "Executive Briefing" }, { id: "linkedin_post", label: "LinkedIn Post" }, { id: "linkedin_article", label: "LinkedIn Article" }, { id: "substack", label: "Substack" }, { id: "video_script", label: "Video Script" }, { id: "ads", label: "Ads" }, ]; /** * Renders blog HTML in a sandboxed preview div. * All content is pipeline-generated (not user input). * DOMPurify sanitization applied before DOM update — defense-in-depth per PREV-04. */ function BlogTab({ article }) { const containerRef = useRef(null); const rawHtml = article.blog_post || article.blog_html || ""; useEffect(() => { if (!containerRef.current || !rawHtml) return; // Sanitize pipeline-generated HTML before rendering (defense-in-depth) const sanitized = typeof DOMPurify !== "undefined" ? DOMPurify.sanitize(rawHtml, { USE_PROFILES: { html: true } }) : rawHtml; // Safe: sanitized content has been processed by DOMPurify containerRef.current.textContent = ""; const wrapper = document.createElement("div"); wrapper.className = "blog-preview prose"; // DOMParser approach: parse sanitized HTML into a doc, then append childNodes const parsed = new DOMParser().parseFromString(sanitized, "text/html"); Array.from(parsed.body.childNodes).forEach(node => wrapper.appendChild(node.cloneNode(true))); containerRef.current.appendChild(wrapper); }, [rawHtml]); if (!rawHtml) { return
No blog content — run generate first.
; } return
; } function TextTab({ content, label }) { if (!content) return
No {label} content yet.
; if (Array.isArray(content)) { return (
{content.map((item, i) => (
{i + 1}. {typeof item === "string" ? item : JSON.stringify(item, null, 2)}
))}
); } return (
{content}
); } function VideoScriptTab({ article }) { const script = article.video_script || article.video_narration || ""; if (!script) return
No video script yet.
; return (
{script}
); } function AdsTab({ article, batchId }) { const [adData, setAdData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { if (article.ad_brief) { setAdData(article.ad_brief); setLoading(false); return; } const month = batchId || new Date().toISOString().slice(0, 7); apiFetch(`/api/ads/${month}`) .then(r => r.json()) .then(d => { const briefs = d.briefs || []; const match = briefs.find(b => b.article_id === article.article_id); setAdData(match || null); setLoading(false); }) .catch(() => { setAdData(null); setLoading(false); }); }, [article.article_id, batchId]); if (loading) return
Loading ad variants...
; if (!adData) { return (
No ad briefs yet — run: uv run pipeline/run.py ads --month {batchId}
); } const variants = adData.variants || []; const targeting = adData.targeting || {}; return (
{adData.boost_candidate &&
Boost Candidate
} {adData.recommended_image && (
Social card: {adData.recommended_image}
)} {Object.keys(targeting).length > 0 && (
Targeting
{targeting.job_titles && (
Titles: {targeting.job_titles.join(", ")}
)} {targeting.industries && (
Industries: {targeting.industries.join(", ")}
)} {targeting.company_size && (
Company size: {targeting.company_size}
)}
)}
{variants.map((v, i) => (
Variant {i + 1}
Headline
{v.headline}
Body
{v.body}
CTA
{v.cta}
))} {variants.length === 0 &&
No ad variants in this brief.
}
); } function ArticleDetail({ article, batchId, articleIndex, onArticleUpdate }) { const [activeTab, setActiveTab] = useState("blog"); const [editTitle, setEditTitle] = useState(article.title || ""); const [editSummary, setEditSummary] = useState(article.summary || ""); const [editCta, setEditCta] = useState(article.cta_text || ""); const [saving, setSaving] = useState(false); useEffect(() => { setEditTitle(article.title || ""); setEditSummary(article.summary || ""); setEditCta(article.cta_text || ""); }, [article.article_id]); function saveEdits(overrides) { if (saving) return; setSaving(true); const resolvedId = batchId || "latest"; apiFetch(`/api/batch/${resolvedId}/article/${articleIndex}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(Object.assign({ title: editTitle, summary: editSummary, cta_text: editCta }, overrides || {})), }) .then(r => r.json()) .then(updated => { onArticleUpdate(articleIndex, updated); setSaving(false); }) .catch(() => setSaving(false)); } return (
setEditTitle(e.target.value)} onBlur={() => saveEdits()} placeholder="Article title" /> {article.quality_score != null && }