/* ============================================================ PULSE — Shared components, icons, demo data, API layer Globals exposed at end. ============================================================ */ const { useState, useEffect, useRef, useMemo } = React; /* ------------------------------ API layer ------------------------------ */ const api = { _token: () => localStorage.getItem("pulse:token") || "", async login(email, password) { const r = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }); if (!r.ok) { const err = await r.json().catch(() => ({})); throw new Error(err.detail || "Login failed"); } return r.json(); }, async get(path) { const r = await fetch(path, { headers: { Authorization: `Bearer ${this._token()}` }, }); if (r.status === 401) { localStorage.removeItem("pulse:token"); window.dispatchEvent(new Event("pulse:logout")); throw new Error("Unauthorized"); } if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }, async put(path, body) { const r = await fetch(path, { method: "PUT", headers: { Authorization: `Bearer ${this._token()}`, "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }, async post(path, body) { const r = await fetch(path, { method: "POST", headers: { Authorization: `Bearer ${this._token()}`, "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }, streamRun(runId, onEvent) { const token = this._token(); const es = new EventSource(`/api/generate/${runId}/stream?token=${encodeURIComponent(token)}`); es.onmessage = (e) => { try { const data = JSON.parse(e.data); onEvent(data); if (data.done) es.close(); } catch (_) {} }; es.onerror = () => es.close(); return es; }, }; /* ------------------------------ Timezone (global) ------------------------------ */ let _appTz = Intl.DateTimeFormat().resolvedOptions().timeZone; function appTz() { return _appTz; } function setAppTz(tz) { if (tz) _appTz = tz; } /* ------------------------------ Icons (lucide-style, stroked) ------------------------------ */ const iconProps = { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round" }; const I = { Pulse: (p) => , Home: (p) => , Play: (p) => , Archive:(p) => , Settings:(p)=> , Search:(p) => , Plus:(p) => , Check:(p) => , X:(p) => , ChevRight:(p) => , ChevDown:(p) => , ArrowRight:(p) => , ArrowUpRight:(p) => , Download:(p) => , Share:(p) => , Calendar:(p) => , Clock:(p) => , Filter:(p) => , Trend:(p) => , Alert:(p) => , Shield:(p) => , History:(p) => , File:(p) => , Moon:(p) => , Sun:(p) => , Sliders:(p) => , Dot:(p) => , Folder:(p) => , LogOut:(p) => , Zap:(p) => , }; /* ------------------------------ RAG helpers ------------------------------ */ const RAG = { red: { cls: "red", label: "RED", pillCls: "pill-red", dotCls: "dot-red" }, amber: { cls: "amber", label: "AMBER", pillCls: "pill-amber", dotCls: "dot-amber" }, green: { cls: "green", label: "GREEN", pillCls: "pill-green", dotCls: "dot-green" }, }; function RagDot({ status, withLabel }) { const r = RAG[status]; return ( {withLabel && {r.label}} ); } function RagPill({ status }) { const r = RAG[status]; return {r.label}; } /* ------------------------------ Report types ------------------------------ */ const REPORT_TYPES = [ { id: "rag", name: "RAG Status", shortName: "RAG", icon: I.Shield, tagline: "Weekly stakeholder-ready narrative with RAG across all workstreams.", desc: "Aggregates Jira data into a narrative status report with RAG indicators per workstream. Claude generates executive summary, risks and next-week priorities.", runtime: "~55s", color: "var(--text)", }, { id: "velocity", name: "Velocity Anomaly", shortName: "Velocity", icon: I.Trend, tagline: "Time-overrun and burn-rate analysis against estimates.", desc: "Detects tasks where logged time exceeds estimate by >15%. Computes per-workstream velocity and flags systemic drift.", runtime: "~22s", color: "var(--amber)", }, { id: "raid", name: "RAID Log", shortName: "RAID", icon: I.Alert, tagline: "Risks, Assumptions, Issues, Dependencies — scored & prioritised.", desc: "Classifies every open issue into R/A/I/D, scores probability × impact (P1-P3) and outputs a sortable log with owners.", runtime: "~34s", color: "var(--red)", }, { id: "pattern", name: "Historical Pattern", shortName: "Pattern", icon: I.History, tagline: "Match current sprint fingerprint to past sprint outcomes.", desc: "Compares current velocity/completion/overdue/blocker/bug ratios against historical sprints to detect failed-sprint patterns before they happen.", runtime: "~41s", color: "var(--blue)", }, ]; /* ------------------------------ Demo generated reports ------------------------------ */ function mkReport(date, overall, metrics = {}) { return { date, overall, generatedAt: `${date} 09:12`, author: "maks@pulse.app", project: "BuildLog (BL)", metrics: { totalIssues: 58, done: 16, inProgress: 20, todo: 22, overdue: 21, blockers: 3, bugs: 10, velocityPct: 23, velocitySp: "84 / 368", ...metrics, }, workstreams: [ { name: "Backend", rag: overall, velocity: 22, completion: 29, done: "6/21", overdue: 7, blockers: 1, bugs: 3, sp: "34 / 156" }, { name: "Frontend", rag: overall === "red" ? "red" : "amber", velocity: 31, completion: 30, done: "6/20", overdue: 7, blockers: 0, bugs: 4, sp: "31 / 100" }, { name: "QA", rag: overall, velocity: 22, completion: 29, done: "4/14", overdue: 4, blockers: 2, bugs: 1, sp: "19 / 88" }, ], }; } const DEMO_REPORTS = [ { id: "r-2026-04-18", type: "rag", ...mkReport("2026-04-18", "red") }, { id: "v-2026-04-18", type: "velocity", ...mkReport("2026-04-18", "red") }, { id: "d-2026-04-18", type: "raid", ...mkReport("2026-04-18", "red") }, { id: "p-2026-04-18", type: "pattern", ...mkReport("2026-04-18", "red") }, { id: "r-2026-04-11", type: "rag", ...mkReport("2026-04-11", "amber", { velocityPct: 42 }) }, { id: "v-2026-04-11", type: "velocity", ...mkReport("2026-04-11", "amber", { velocityPct: 42 }) }, { id: "d-2026-04-11", type: "raid", ...mkReport("2026-04-11", "amber") }, { id: "p-2026-04-11", type: "pattern", ...mkReport("2026-04-11", "amber") }, { id: "r-2026-04-04", type: "rag", ...mkReport("2026-04-04", "amber", { velocityPct: 58 }) }, { id: "v-2026-04-04", type: "velocity", ...mkReport("2026-04-04", "amber", { velocityPct: 58 }) }, { id: "d-2026-04-04", type: "raid", ...mkReport("2026-04-04", "amber") }, { id: "p-2026-04-04", type: "pattern", ...mkReport("2026-04-04", "amber") }, { id: "r-2026-03-28", type: "rag", ...mkReport("2026-03-28", "green", { velocityPct: 82 }) }, { id: "v-2026-03-28", type: "velocity", ...mkReport("2026-03-28", "green", { velocityPct: 82 }) }, { id: "d-2026-03-28", type: "raid", ...mkReport("2026-03-28", "green") }, { id: "p-2026-03-28", type: "pattern", ...mkReport("2026-03-28", "green") }, { id: "r-2026-03-21", type: "rag", ...mkReport("2026-03-21", "green", { velocityPct: 79 }) }, { id: "v-2026-03-21", type: "velocity", ...mkReport("2026-03-21", "green", { velocityPct: 79 }) }, ]; function reportTypeMeta(typeId) { return REPORT_TYPES.find(r => r.id === typeId); } /* ------------------------------ Common UI pieces ------------------------------ */ function Logo({ size = 22 }) { return (
); } function Sidebar({ page, onNav, onLogout, unread = 3 }) { const items = [ { id: "dashboard", label: "Dashboard", icon: I.Home }, { id: "generate", label: "Generate", icon: I.Zap }, { id: "archive", label: "Archive", icon: I.Archive, count: 42 }, ]; const items2 = [ { id: "settings", label: "Settings", icon: I.Settings }, ]; return (
onNav("dashboard")}>
Pulse
v1.0
onNav("dashboard")}>
BuildLog buildlog001.atlassian.net
Workspace
{items.map(it => { const Icon = it.icon; return (
onNav(it.id)}> {it.label} {it.count != null && {it.count}}
); })}
Reports
{REPORT_TYPES.map(t => { const Icon = t.icon; return (
onNav("archive", { type: t.id })}> {t.shortName}
); })}
Account
{items2.map(it => { const Icon = it.icon; return (
onNav(it.id)}> {it.label}
); })}
onNav("settings")}>MK
onNav("settings")}> Maks K. Delivery Manager
onLogout ? onLogout() : onNav("login")} />
); } function Topbar({ crumbs, onNav, onOpenCmd, right }) { return (
{crumbs.map((c, i) => ( {i > 0 && /} c.to && onNav(c.to)} > {c.label} ))}
{right}
); } /* ------------------------------ Sparkline ------------------------------ */ function Sparkline({ values, color = "var(--text)", height = 32, width = 140, showArea = true }) { const max = Math.max(...values); const min = Math.min(...values); const range = max - min || 1; const step = width / (values.length - 1); const points = values.map((v, i) => `${i * step},${height - ((v - min) / range) * (height - 4) - 2}`).join(" "); const areaPath = `0,${height} ${points} ${width},${height}`; return ( {showArea && } ); } /* ------------------------------ Tweaks panel ------------------------------ */ function TweaksPanel({ open, onClose, theme, setTheme, direction, setDirection, density, setDensity }) { if (!open) return null; return (
Tweaks
Direction
{[ { id: "classic", label: "Classic" }, { id: "command", label: "Command" }, { id: "editorial", label: "Editorial" }, ].map(d => ( ))}
Theme
{[{ id: "light", label: "Light", Icon: I.Sun }, { id: "dark", label: "Dark", Icon: I.Moon }].map(t => ( ))}
Density
{[{ id: "comfortable", label: "Comfortable" }, { id: "compact", label: "Compact" }].map(d => ( ))}
Try each direction — Classic is Linear-like, Command is terminal-dense, Editorial is calm + serif.
); } /* ------------------------------ Command palette ------------------------------ */ function CommandPalette({ open, onClose, onNav }) { const [q, setQ] = useState(""); const inputRef = useRef(null); useEffect(() => { if (open) setTimeout(() => inputRef.current?.focus(), 50); }, [open]); if (!open) return null; const items = [ { label: "Go to Dashboard", action: () => onNav("dashboard"), kind: "Navigate", Icon: I.Home }, { label: "Generate reports", action: () => onNav("generate"), kind: "Action", Icon: I.Zap }, { label: "Browse archive", action: () => onNav("archive"), kind: "Navigate", Icon: I.Archive }, { label: "Open settings", action: () => onNav("settings"), kind: "Navigate", Icon: I.Settings }, ...REPORT_TYPES.map(r => ({ label: `Run ${r.name} report`, action: () => onNav("generate", { preselect: [r.id] }), kind: "Run", Icon: r.icon })), ...DEMO_REPORTS.slice(0, 6).map(r => ({ label: `${reportTypeMeta(r.type).name} — ${r.date}`, action: () => onNav("report", { reportId: r.id }), kind: "Open", Icon: reportTypeMeta(r.type).icon, })), ]; const filtered = items.filter(it => it.label.toLowerCase().includes(q.toLowerCase())); return (
e.stopPropagation()} style={{ width: 560, background: "var(--surface)", border: "1px solid var(--border)", borderRadius: "var(--r-lg)", boxShadow: "var(--sh-lg)", overflow: "hidden" }}>
setQ(e.target.value)} placeholder="Search or run a command…" style={{ flex: 1, border: "none", outline: "none", background: "transparent", fontSize: 15, color: "var(--text)", fontFamily: "inherit" }} /> ESC
{filtered.length === 0 &&
No matches for "{q}"
} {filtered.map((it, i) => (
{ it.action(); onClose(); }} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 10px", borderRadius: 6, cursor: "pointer", fontSize: 13 }} onMouseEnter={e => e.currentTarget.style.background = "var(--surface-2)"} onMouseLeave={e => e.currentTarget.style.background = "transparent"} > {it.label} {it.kind}
))}
); } /* ------------------------------ Export globals ------------------------------ */ Object.assign(window, { api, I, RAG, RagDot, RagPill, REPORT_TYPES, DEMO_REPORTS, reportTypeMeta, mkReport, Logo, Sidebar, Topbar, Sparkline, TweaksPanel, CommandPalette, useState, useEffect, useRef, useMemo, });