- 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>
41 lines
1.7 KiB
HTML
41 lines
1.7 KiB
HTML
<div class="contact-card">
|
|
<div class="cc-avatar" id="cc-avatar">?</div>
|
|
<h2 id="cc-name">—</h2>
|
|
<div class="cc-fields" id="cc-fields"></div>
|
|
</div>
|
|
|
|
<style>
|
|
.contact-card { padding: 28px 24px; font-family: -apple-system, sans-serif; text-align: center; width: 280px; }
|
|
.cc-avatar { width: 64px; height: 64px; background: #e8e8e8; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 26px; font-weight: 600; color: #666; margin: 0 auto 14px; }
|
|
.contact-card h2 { font-size: 18px; font-weight: 600; color: #1a1a1a; margin-bottom: 14px; }
|
|
.cc-fields { text-align: left; display: flex; flex-direction: column; gap: 8px; }
|
|
.cc-field { font-size: 13px; color: #888; }
|
|
.cc-field span { font-weight: 500; color: #333; }
|
|
.cc-empty { color: #aaa; font-size: 13px; }
|
|
</style>
|
|
|
|
<script>
|
|
(function () {
|
|
const props = window.__componentProps || {}
|
|
const id = props.id || props.contactId
|
|
if (!id) {
|
|
document.getElementById('cc-name').textContent = 'No contact selected'
|
|
return
|
|
}
|
|
fetch(`/apps/contacts/${id}`)
|
|
.then(r => r.ok ? r.json() : Promise.reject())
|
|
.then(c => {
|
|
document.getElementById('cc-avatar').textContent = (c.name || '?')[0].toUpperCase()
|
|
document.getElementById('cc-name').textContent = c.name || '—'
|
|
const fields = document.getElementById('cc-fields')
|
|
fields.innerHTML = [
|
|
c.domain ? `<div class="cc-field">Domain: <span>${c.domain}</span></div>` : '',
|
|
c.notes ? `<div class="cc-field">Notes: <span>${c.notes}</span></div>` : '',
|
|
].join('')
|
|
if (!fields.innerHTML) fields.innerHTML = '<div class="cc-empty">No details.</div>'
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('cc-name').textContent = 'Contact not found'
|
|
})
|
|
})()
|
|
</script>
|