/* Waypath Platform — premium Linear/Cursor view components. */ const { useState, useEffect, useRef, useMemo, useCallback } = React; const D = window.WP_DATA; /* ──────── primitives ──────── */ function Arr() { return ; } /* ──────── strength strip ──────── Port of from packages/web/src/components/ instrument/ModuleCard.tsx. 8 segments by default — solid vermillion for `lit`, 40% vermillion for `warn`, muted for the remainder. Reads as a signal-strength bar on a dark grid backdrop. */ function StrengthStrip({ total = 8, lit = 0, warn = 0, pattern }) { // If a `pattern` array is given, each entry ("lit" | "on" | "warn" | "off") // controls a segment directly — used to render the exact 10-bar signal // burst from the reference (orange lit bars are taller; white "on" bars // and dim gray "off" bars share the shorter baseline height — colors // and heights live in styles.css under .strength-strip .seg). if (Array.isArray(pattern)) { return (
{pattern.map((p, i) => { const cls = (p === "lit" || p === "on" || p === "warn" || p === "off") ? p : "off"; return ; })}
); } const stripTotal = total; const stripLit = Math.max(0, Math.min(stripTotal, lit)); const stripWarn = Math.max(0, Math.min(stripTotal - stripLit, warn)); return (
{Array.from({ length: stripTotal }).map((_, i) => { const cls = i < stripLit ? "lit" : i < stripLit + stripWarn ? "warn" : "off"; return ; })}
); } /* ──────── TickStrip ──────── Radio-channel / signal-meter indicator. Three height+color tiers: `lo` (10px, black @ 25%), `md` (10px, solid black), `hi` (14px, vermillion, vertically centered via -2px margin so it overhangs the baseline equally above and below). Sharp rectangles — no radius, no shadow, no animation. Spec lives in uploads/strength-strip-prompt-*.md. */ const TICK_STYLES = { lo: { width: 2, height: 10, background: 'rgba(8, 8, 8, 0.25)', borderRadius: 0 }, md: { width: 2, height: 10, background: '#080808', borderRadius: 0 }, hi: { width: 2, height: 14, background: '#E8522B', margin: '-2px 0', borderRadius: 0 }, }; function TickStrip({ ticks }) { return (
{ticks.map((level, i) => ( ))}
); } function Btn({ children, onClick, ghost, tiny, full, notched }) { return ( ); } /* ──────── ASCII status icon ──────── Replaces the standard dot/spinner with a small canvas rendering of the Waypath ASCII engine. Each status maps to a different shape from the engine. */ const STATUS_SHAPE = { run: "sphere", ready: "octahedron", wait: "rings", done: "cube" }; function AsciiStatus({ status = "run", size = 22, large = false, invertMouse = false }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el || !window.AsciiEngine) return; if (el.dataset.asciiInited) return; el.dataset.asciiInited = "1"; const isDone = status === "done"; window.AsciiEngine.init(el, { mode: "sphere", shape: STATUS_SHAPE[status] || "sphere", interactive: invertMouse, invertMouse, scanlines: false, bgAlpha: 0, radius: large ? 0.42 : 0.52, font: large ? 12 : 7, cellW: large ? 7 : 4, cellH: large ? 9 : 5, weight: 700, lightColor: isDone ? [120, 120, 120] : [255, 110, 50], baseColor: isDone ? [70, 70, 70] : [200, 195, 185] }); }, [status]); return ; } /* ──────── status helpers ──────── */ const STATUSES = { high: { id: "run", label: "In progress", dot: "run" }, warm: { id: "ready", label: "Ready for review", dot: "ready" }, cool: { id: "done", label: "Waiting on you", dot: "wait" } }; function statusOf(o) { return STATUSES[o.stage] || STATUSES.warm; } /* ──────── row (agent task) ──────── */ function Row({ o, selected, onSelect, idx }) { const s = statusOf(o); const customerName = o.who.split(" · ")[0]; const taskBlurb = o.who.split(" · ")[1] || ""; return (
onSelect(o)}>
{customerName} — {taskBlurb} {o.id}
{o.signal.toLowerCase().replace(/\b\w/g, c => c.toUpperCase())} {o.playbook}
{o.conf}%
{o.agent.split(" · ")[0].toLowerCase().replace(/\b\w/g, c => c.toUpperCase())}
{(o.agent.split(" · ")[1] || "").toLowerCase()} {idx + 1}
); } function CustomerRow({ c, selected, onSelect, idx }) { const stage = c.health > 75 ? "high" : c.health < 50 ? "cool" : "warm"; const s = STATUSES[stage]; return (
onSelect(c)}>
{c.name} — {c.contact || "no contact"}, {c.role} {c.id}
{c.risk} ${c.arr} ARR · {c.plan}
{c.health}
{c.seats} seat
{c.lastEvent.split(" · ")[c.lastEvent.split(" · ").length - 1].toLowerCase()} {idx + 1}
); } /* ──────── group header ──────── */ function GroupH({ label, count, meta, open = true, onToggle }) { return (
{open ? "▾" : "▸"} {label} {count} {meta ? {meta} : null}
); } /* ──────── subhead (filter / view bar) ──────── */ function Subhead({ dest, view, setView, count, todayTab, setTodayTab }) { const t = dest === "today" ? { h: "Today", m: "Friday, 15 May" } : dest === "pipeline" ? { h: "Pipeline", m: `${count} open opportunities` } : { h: "Customers", m: "2,148 in book" }; const views = dest === "customers" ? [{ id: "all", l: "All", c: 2148 }, { id: "high", l: "Healthy", c: 1842 }, { id: "warm", l: "Watch", c: 268 }, { id: "cool", l: "At-risk", c: 38 }] : [{ id: "all", l: "All", c: 42 }, { id: "high", l: "In progress", c: 3 }, { id: "warm", l: "Ready", c: 2 }, { id: "cool", l: "Waiting", c: 1 }]; const showSubtabs = dest === "today"; const inReports = showSubtabs && todayTab === "reports"; const freshReports = D.reports ? D.reports.filter(r => r.fresh).length : 0; return (

{t.h}

· {t.m} {showSubtabs ? (
) : null} {inReports ? (
) : (
{views.map(v => ( ))}
)}
); } /* ──────── INSPECTOR — customer detail ──────── */ function CustomerInspector({ data, onFire, onClose }) { const s = data; return ( <>
{s.name} · {s.company} {s.id}
Expansion window · open · 6–9d typical

The expansion window is open.

{s.role} · {s.plan} · stitched across 14 sessions in the last week
Health
{s.health}/100
ARR
${s.arr}USD
NPS
{s.nps}/10
Tenure
{s.daysIn}days
Signal · why this
Sarah's team added 4 seats and visited /pricing five times in 48h. Pattern matches PPL-04 · expand-loop with 94% confidence.
Journeylast 72h Show all
{s.journey.slice(0, 6).map((j, i) => (
{j.t}
{j.what}{j.note}{j.src}
))}
Stakeholders4 identified Add
{s.stakeholders.map((p, i) => { const tone = p.touch.startsWith("active") ? "" : p.touch.startsWith("warm") ? "warm" : "cold"; return (
{p.name}{p.you ? you : null}
{p.role.toLowerCase().replace(/\b\w/g, c => c.toUpperCase())}
{p.touch}
); })}
PlaybookPPL-04 · expand-loop Customize
T-0
Acknowledge seat add→ Slack to Sarah
T+2h
Share role-mapping doc→ via SSO thread
T+1d
Loop CTO Marcus→ exec digest email
T+3d
Pricing proposal · 40 seat→ HubSpot deal
T+7d
Procurement intro→ Jenn Park
Fire PPL-04 Draft outreach
); } /* ──────── INSPECTOR — opportunity detail ──────── */ function OppInspector({ o, onFire, onOpenCustomer, onClose }) { const stageCopy = o.stage === "high" ? "Fire now." : o.stage === "warm" ? "Worth a look." : "Re-warm."; return ( <>
{o.who.split(" · ")[0]} · {o.who.split(" · ")[1]} {o.id}
{o.signal.toLowerCase().replace(/\b\w/g, c => c.toUpperCase())} · {o.playbook}

{stageCopy}

{o.sub}
Confidence
{o.conf}%
Rank
#1
Signals
3hits
Age
{(o.agent.split(" · ")[1] || "—").replace(/[^0-9]/g, "") || "—"}{(o.agent.split(" · ")[1] || "").replace(/[0-9]/g, "").trim().toLowerCase()}
Signal · {o.signal.toLowerCase()}
{o.sub}. Surfaced by {o.agent.split(" · ")[0].toLowerCase()}. Match to {o.playbook} exceeds threshold.
Proposed playbook{o.playbook} Swap
T-0
Acknowledge signal→ logged to {o.id}
T+1h
Send tailored outreach→ Customer.io
T+1d
Loop stakeholders→ HubSpot deal
T+3d
Follow-up · score check→ revenue agent
What the agent sawlog Raw
17m
Score crossed 90{o.signal.toLowerCase()} + recency bumpsignal
38m
Stitched identities14 → 3 stakeholdersorchestrator
2h
Event ingest spike+ 4.2k/hr thresholdsegment
Fire {o.playbook.split(" ")[0]} Open customer
); } /* ──────── FIRST RUN ──────── */ function FirstRunView({ onSuggest }) { const picks = [ { ic: "solid", lab: "Who should I reach out to today?", k: "1", q: "today" }, { ic: "", lab: "Which customers are at risk?", k: "2", q: "risk" }, { ic: "", lab: "Show me the pipeline by stage", k: "3", q: "pipeline" }, { ic: "", lab: "What did the agents do overnight?", k: "4", q: "log" } ]; return (
Demo workspace · 6 customers · 42 opportunities

Pick a thread.

We seeded a workspace so nothing is empty. Pick a question, or ask the agent below. Real data swaps in when you connect integrations.

{picks.map(p => ( ))}
); } /* ──────── AGENT THREAD ──────── */ function UserMsg({ time, children }) { return (
You · {time}
{children}
); } function AgentMsg({ agent = "orchestrator", duration, children }) { return (
{agent}{duration ? · {duration} : null}
{children}
); } function AgentSteps({ steps }) { return (
{steps.map((s, i) => (
{s.done ? "✓" : "›"} {s.text} {s.meta ? {s.meta} : null}
))}
); } function ToolCall({ tool, status, target }) { return (
{tool} · {status} {target ? <>·{target} : null}
); } function DraftCard({ subject, body, channel = "customer.io" }) { return (
draft · email · {channel}
tosarah@techflow.com
subject{subject}
{body}
); } function TypingIndicator({ agent, action }) { return (
{agent} {action}
); } function ThreadBg() { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el || !window.AsciiEngine) return; if (el.dataset.asciiInited) return; el.dataset.asciiInited = "1"; // ONE canvas, sphere mode, sphere offset to the right. // Engine renders the wave-field around the sphere automatically, // so the whole pane reads as a single continuous atmosphere // — same approach as Waypath.html hero. window.AsciiEngine.init(el, { mode: "sphere", shape: "sphere", interactive: true, scanlines: false, bgAlpha: 0, radius: 0.46, offsetX: 180, offsetY: -60, font: 13, cellW: 8, cellH: 14, weight: 500, lightColor: [255, 110, 50], baseColor: [160, 148, 130] }); }, []); return ; } function ThreadView({ onOpenCustomer, onBack, onFire }) { return ( <>

Agent thread

· orchestrator · 3 turns · 4 tool calls
thread · live
RPL-088·orchestrator·3 turns tool·customer.io sent watching·Northbeam thread · CIO replied 38s ago RPL-088·orchestrator·3 turns tool·customer.io sent watching·Northbeam thread · CIO replied 38s ago
{/* ── Turn 1 ── */} Who should I reach out to today?

{D.replies.today.preamble}

{D.replies.today.cards.map((c, i) => (
{c.num}% · {c.id} · rank {i + 1}
{c.who}

Open {c.fire.charAt(0).toUpperCase() + c.fire.slice(1).toLowerCase().replace(/(ppl|opp|rpt|cst|jny|stk)-(\d+)/gi, (_, p, n) => `${p.toUpperCase()}-${n}`)}
))} {/* ── Turn 2 ── */} draft the sarah chen outreach. personal, short.

Draft below. Warm, direct, 62 words. Pre-warmed with the role-mapping doc she asked about in Slack this morning. Ready to send.

hey sarah —

saw the four new seats this morning. role-mapping doc you flagged in #integrations is attached. happy to walk the eng-platform crew through it together, otherwise feel free to forward.

also — if you're sizing this past 40 seats, we have growth pricing that gets you there for less than upgrading one-by-one. want me to send the breakdown?

— sc } /> {/* ── Turn 3 ── */} drop the second paragraph. send it.

Sent at 14:04 UTC. Logged to OPP-014. Watching for reply — I'll ping you on signal.

{/* ── live: agent still working ── */}
); } /* ──────── reports view (Today → Reports sub-tab) ──────── */ function ReportsView({ onSelect }) { const reports = (D.reports || []); const TYPE_LABEL = { digest: "DIGEST", sweep: "SWEEP", forecast: "FORECAST", ticket: "INCIDENT" }; return (
Reports Auto-generated by the agents. New runs surface as fresh. Saved reports persist across days.
{reports.map((r, i) => (
onSelect && onSelect(r)} >
{r.id} {r.t} · {TYPE_LABEL[r.type] || r.type.toUpperCase()} {r.fresh ? ( new ) : null}

{r.title}

by {r.author}

{r.summary}

{r.stats && r.stats.length ? (
{r.stats.map((s, si) => (
{s.k}
{s.v}{s.u}
))}
) : null} {r.highlights && r.highlights.length ? (
    {r.highlights.map((h, hi) => (
  • {h.who} {h.note}
  • ))}
) : null}
Open report  save S share ⇧⌘S
))}
End of feed Older runs are archived to RPT log · keyboard L to open.
); } /* ──────── live ticker ──────── */ function Ticker() { const events = D.activity; // duplicate for seamless loop const lane = [...events, ...events]; return (
live · 24h
{lane.map((e, i) => ( {e.t} {e.from.toLowerCase()} · ${e.msg}` : e.msg }} /> ))}
); } /* ──────── atmospheric ASCII field ──────── */ function AtmosField() { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el || !window.AsciiEngine) return; if (el.dataset.asciiInited) return; el.dataset.asciiInited = "1"; window.AsciiEngine.init(el, { mode: "field", interactive: false, scanlines: false, bgAlpha: 0, fieldAlphaMax: 0.55, font: 11, cellW: 7, cellH: 12, weight: 500, baseColor: [100, 92, 80] }); }, []); return ; } window.WP_VIEWS = { Btn, Arr, Row, CustomerRow, GroupH, Subhead, CustomerInspector, OppInspector, FirstRunView, ThreadView, ReportsView, STATUSES, AsciiStatus, Ticker, AtmosField, ThreadBg, StrengthStrip, TickStrip };