// Loop MVP — People + Agent screens const Dp = () => window.LOOP_DATA; // ───────────────────────────────────────────────────────────── // PeopleScreen — "my people" view with grouping & filters // ───────────────────────────────────────────────────────────── function PeopleScreen({ persona, go }) { const people = Dp().people; const [filter, setFilter] = React.useState('all'); const [query, setQuery] = React.useState(''); const filters = [ { key: 'all', label: 'Everyone' }, { key: 'needs-attention', label: 'Needs a touch' }, { key: 'Investors', label: 'Investors' }, { key: 'Team', label: 'Team' }, { key: 'Candidates', label: 'Candidates' }, { key: 'Customers', label: 'Customers' }, ]; const filtered = people.filter(p => { if (filter !== 'all' && filter !== 'needs-attention' && p.group !== filter) return false; if (filter === 'needs-attention' && p.status !== 'needs-attention') return false; if (query && !p.name.toLowerCase().includes(query.toLowerCase())) return false; return true; }); // group const groups = {}; filtered.forEach(p => { (groups[p.group] = groups[p.group] || []).push(p); }); return (
setQuery(e.target.value)} placeholder="Search 248 people" style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 450 }} />
{filters.map(f => ( ))}
{Object.entries(groups).map(([groupName, list]) => (
{list.map((p, i) => ( } title={p.name} subtitle={`${p.role} · last ${p.last}`} trailing={
} onClick={() => go('person', { id: p.id })} /> ))}
))}
); } // ───────────────────────────────────────────────────────────── // PersonScreen — relationship detail // ───────────────────────────────────────────────────────────── function PersonScreen({ persona, go, params }) { const p = Dp().people.find(x => x.id === params?.id) || Dp().people[0]; const dossier = (Dp().dossiers && (Dp().dossiers[p.id] || Dp().dossiers._default)) || { pillars: [] }; const timeline = [ { when: 'Today 09:30', kind: 'meeting', body: 'Series C check-in · upcoming', detail: 'You + Jess Lee' }, { when: 'Tue', kind: 'email', body: 'Asked for an updated cap table.', detail: 'Inbound · Gmail' }, { when: 'May 5', kind: 'meeting', body: 'Board call · captured by Loop', detail: '7 decisions, 12 actions' }, { when: 'Apr 28', kind: 'note', body: 'Birthday on May 23. Likes single-origin coffee.', detail: 'Your note' }, { when: 'Apr 14', kind: 'meeting', body: 'First Series C conversation', detail: 'Loop summary' }, ]; return (
go('people')} title="" trailing={ } /> {/* Hero portrait */}

{p.name}

{p.role}
{p.status.replace('-', ' ')} last {p.last}
{/* Next move */}
{p.next}
Loop will brief you 30 minutes before the meeting.
{/* CIA-level dossier */}
Updated 12m}>
Confidential · your eyes only

Assembled by Loop from CRM, public sources, your inbox, and prior meetings.

{dossier.pillars.map((pillar, i) => (
{String(i + 1).padStart(2, '0')} {pillar.label}

{pillar.body}

))}
{/* Quick stats */}
{[ { label: 'Touches · 30d', value: '7' }, { label: 'Avg response', value: '4h' }, { label: 'Open threads', value: '2' }, ].map((s, i) => (
{s.value}
{s.label}
))}
{/* Timeline */}
{timeline.map((t, i) => { const ICON = { meeting: 'video', email: 'mail', note: 'sticky-note' }[t.kind]; return (
{t.when}
{t.kind}
} title={t.body} subtitle={t.detail} trailing={} /> ); })}
); } // ───────────────────────────────────────────────────────────── // AgentsScreen — agent library / dashboard // ───────────────────────────────────────────────────────────── function AgentsScreen({ persona, go }) { const agents = Dp().agents[persona]; return (
go('agent-builder')} style={{ width: 40, height: 40, borderRadius: '50%', background: 'var(--ink)', color: 'var(--canvas)', border: 'none', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}> } />
Now running
{agents.length}
agents · {agents.reduce((s, a) => s + parseInt(a.runs), 0)} runs today
All routed to your inbox, your CRM, and your daily brief. Tap any agent to coordinate.
{agents.map((a, i) => ( go('agent-chat', { id: a.id })} style={{ marginBottom: 10, padding: 20 }}>
{a.name}
{a.live && Live}
{a.role}
{a.skills} skills · {a.runs}
))}
{[ { name: 'Investor relations', desc: 'Briefs + cap table awareness.', tint: '#CF4500' }, { name: 'Customer success', desc: 'Renewal radar + health scores.', tint: '#3860BE' }, { name: 'Deal review', desc: 'Reads CRM, flags risk.', tint: '#1F7A4D' }, { name: 'Recruiting', desc: 'Pipeline keeper + panel summaries.', tint: '#9A3A0A' }, ].map((s, i) => (
{s.name}
{s.desc}
))}
go('agent-builder')}>
Build your own agent
No code. Define role, data, triggers, outputs.
); } // ───────────────────────────────────────────────────────────── // AgentChatScreen — converse with one agent // ───────────────────────────────────────────────────────────── function AgentChatScreen({ persona, go, params }) { const agent = Dp().agents[persona].find(a => a.id === params?.id) || Dp().agents[persona][0]; const seed = persona === 'principal' ? [ { from: 'agent', body: `Aurélien — I pulled three things for Sequoia at 09:30.\n\n1. Roelof opened the cap table 04:12 ago and spent 4 minutes on slide 7. He's anchoring on dilution.\n\n2. Jess Lee has never been in a Helix call. I added her LinkedIn and the four founders she's backed in the last 12 months to your brief.\n\n3. Two open threads from last call. The September close timeline is the riskier one to address.` }, { from: 'user', body: 'What\'s my best move on the timeline?' }, { from: 'agent', body: `Buy two weeks. Frame it as diligence depth, not hesitation. Suggested line: "We're nine days out from clean Q1 numbers and a hiring close. I'd like the term sheet in hand by August 5, signed by August 19." That gives you headroom without losing momentum.` }, ] : [ { from: 'agent', body: `Sana — Mae Chen at 09:30. Three things to know.\n\n1. Devon's round-1 notes landed last night: 7/10 systems design, hesitation on cross-functional conflict. I've prepped a probe for that.\n\n2. Mae is at final stage at Anthropic. Their offer hits her inbox by Friday. We have today and tomorrow.\n\n3. Comp expectation in round 1: $340 base, 1.0% equity. That's inside our band.` }, { from: 'user', body: 'Compare Mae and Yara for me.' }, { from: 'agent', body: `Opening the comparison view. Mae leads on cultural fit (8 vs 6), Yara on scale jump readiness (9 vs 6). Compensation ask is similar. Decision frame: do you need a builder (Mae) or a scaler (Yara)?` }, ]; const [messages, setMessages] = React.useState(seed); const [input, setInput] = React.useState(''); const scrollRef = React.useRef(null); React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages]); const send = () => { if (!input.trim()) return; const q = input.trim(); setMessages(m => [...m, { from: 'user', body: q }]); setInput(''); setTimeout(() => { setMessages(m => [...m, { from: 'agent', body: `Working on it. Pulling from CRM, your inbox, and the dataroom. A draft is ready — review it in 30 seconds.`, thinking: true }]); }, 600); }; return (
go('agents')} title={
{agent.name}
Active · {agent.skills} skills
} trailing={} />
{messages.map((msg, i) => (
{msg.body}
))}
{/* Quick-action chips */}
{['Brief me on next', 'Draft a follow-up', 'Compare candidates', 'Status on Series C'].map((s, i) => ( ))}
{/* Input */}
setInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && send()} placeholder={`Ask ${agent.name.toLowerCase()} anything`} style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', padding: '12px 14px', fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 450 }} />
); } // ───────────────────────────────────────────────────────────── // AgentBuilderScreen — no-code agent builder // ───────────────────────────────────────────────────────────── function AgentBuilderScreen({ persona, go }) { const [name, setName] = React.useState('Fundraising radar'); const [role, setRole] = React.useState('Track every conversation with every potential lead investor. Warn me when one goes 5+ days cold.'); const [skills, setSkills] = React.useState(['s1', 's3', 's5']); const [triggers, setTriggers] = React.useState(['daily', 'meeting-end']); const [step, setStep] = React.useState(0); const allSkills = Dp().skills; const stepLabels = ['Identity', 'Skills', 'Triggers', 'Review']; const toggle = (set, val, fn) => { fn(set.includes(val) ? set.filter(s => s !== val) : [...set, val]); }; return (
go('agents')} title="New agent" eyebrow={`Step ${step + 1} of ${stepLabels.length}`} /> {/* Stepper */}
{stepLabels.map((_, i) => (
))}
{step === 0 && (
setName(e.target.value)} style={{ width: '100%', marginTop: 8, padding: '12px 0', border: 'none', borderBottom: '1.5px solid var(--ink)', background: 'transparent', fontFamily: 'var(--font-sans)', fontSize: 22, fontWeight: 500, letterSpacing: '-0.02em', outline: 'none' }} />