crimata-os/ui/components/contacts/list.html
Andrew Gundersen 31b5e321a1 Add crimata-agent and redesign agentic OS UI
- crimata-agent (TypeScript, port 7702): WebSocket server + HTTP /events
  endpoint, Claude tool-use loop (render/remove/call_api/show_cursor_response),
  session.start fires on first browser connection to seed the canvas
- OS UI redesigned: light grey canvas (#e8e8e8), no dock bar, cursor dot,
  Cmd+Space input bubble above cursor, elements placed by agent
- Canvas elements: app_icon (floating icons with running dot), window (no-chrome
  content cards), cursor_response (text + action pills)
- Component system: HTML fragments fetched from /components/{app}/{name}.html,
  inline scripts re-executed after injection
- contacts/crimata.json: added icon, defaultComponent, components[], api[]
- dock: apps.h/c/main.c extended to parse and serve components + api from
  crimata.json; BUF_SIZE increased for richer JSON output
- ui/components/contacts/list.html + card.html: first component pair
- provision.sh: installs crimata-agent, adds WebSocket + /events + /components
  nginx locations, fixes /auth/ prefix stripping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 15:52:05 -05:00

47 lines
2 KiB
HTML

<div class="contacts-list">
<div class="cl-header">
<h2>Contacts</h2>
<button data-event="new_contact">+ New</button>
</div>
<div class="cl-items" id="cl-items">
<div class="cl-empty">Loading…</div>
</div>
</div>
<style>
.contacts-list { padding: 20px 24px; font-family: -apple-system, sans-serif; width: 360px; }
.cl-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.cl-header h2 { font-size: 17px; font-weight: 600; color: #1a1a1a; }
.cl-header button { background: #1a1a1a; color: white; border: none; border-radius: 8px; padding: 6px 14px; font-size: 12px; font-weight: 500; cursor: pointer; }
.cl-header button:hover { background: #333; }
.contact-row { display: flex; align-items: center; gap: 12px; padding: 9px 10px; border-radius: 10px; cursor: pointer; transition: background 0.1s; }
.contact-row:hover { background: #f5f5f5; }
.contact-avatar { width: 34px; height: 34px; background: #e8e8e8; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; color: #666; flex-shrink: 0; }
.contact-name { font-size: 14px; font-weight: 500; color: #1a1a1a; }
.contact-domain { font-size: 12px; color: #999; }
.cl-empty { color: #aaa; font-size: 13px; padding: 12px 0; }
</style>
<script>
(function () {
const list = document.getElementById('cl-items')
fetch('/apps/contacts/')
.then(r => r.json())
.then(contacts => {
if (!contacts.length) {
list.innerHTML = '<div class="cl-empty">No contacts yet.</div>'
return
}
list.innerHTML = contacts.map(c => `
<div class="contact-row" data-id="${c.id}">
<div class="contact-avatar">${(c.name || '?')[0].toUpperCase()}</div>
<div>
<div class="contact-name">${c.name || '—'}</div>
<div class="contact-domain">${c.domain || ''}</div>
</div>
</div>
`).join('')
})
.catch(() => { list.innerHTML = '<div class="cl-empty">Could not load contacts.</div>' })
})()
</script>