/** * Team Activity Log — Surface Entry Point * * Demonstrates: * - SDK boot + sw.* API access * - Preact rendering into #extension-mount * - Calling Starlark-backed ext API routes (/s/:slug/api/*) * - sw.toast, sw.auth, sw.on (WS event subscription) * - sw.userMenu for shell integration * - Platform CSS variables for theming */ (async function () { 'use strict'; var mount = document.getElementById('extension-mount'); if (!mount) return; // ── Boot SDK (extension surfaces don't auto-load it) ── var base = window.__BASE__ || ''; var ver = window.__VERSION__ || '0'; var slug = (window.__MANIFEST__ || {}).id || 'team-activity-log'; try { if (!window.preact) { var { h, render } = await import(base + '/js/sw/vendor/preact.module.js'); var hooksModule = await import(base + '/js/sw/vendor/hooks.module.js'); var htmModule = await import(base + '/js/sw/vendor/htm.module.js'); window.preact = { h, render }; window.hooks = hooksModule; window.html = htmModule.default.bind(h); } var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver); await sdk.boot(); } catch (e) { mount.innerHTML = '

SDK boot failed: ' + e.message + '

'; return; } var { html } = window; var { useState, useEffect, useCallback, useRef } = hooks; var { render } = preact; // ── API helper: call Starlark backend ── var API_BASE = base + '/s/' + slug + '/api'; async function api(method, path, body) { var token = sw.auth._getToken(); var opts = { method: method, headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, }; if (body) opts.body = JSON.stringify(body); var resp = await fetch(API_BASE + path, opts); var text = await resp.text(); try { return JSON.parse(text); } catch (_) { return { _raw: text, _status: resp.status }; } } // ── Components ───────────────────────────── var CATEGORIES = ['note', 'update', 'decision', 'action-item']; function App() { var [entries, setEntries] = useState([]); var [loading, setLoading] = useState(true); var [filter, setFilter] = useState(''); var menuRef = useRef(null); var load = useCallback(async function () { var path = '/entries' + (filter ? '?category=' + encodeURIComponent(filter) : ''); var d = await api('GET', path); setEntries(d.data || []); setLoading(false); }, [filter]); useEffect(function () { load(); }, [load]); // Mount user menu useEffect(function () { if (menuRef.current && sw.userMenu) { sw.userMenu(menuRef.current, { placement: 'down-right' }); } }, []); async function addEntry(category, message) { var user = sw.auth.user; var result = await api('POST', '/entries', { category: category, message: message, username: user ? (user.display_name || user.username) : '', created_at: new Date().toISOString(), }); if (result.error) { sw.toast(result.error, 'error'); return; } sw.toast('Entry added', 'success'); load(); } async function deleteEntry(id) { await api('DELETE', '/entries/' + id); sw.toast('Entry deleted', 'info'); load(); } return html`

Activity Log

Team activity tracker
<${EntryForm} onSubmit=${addEntry} />
${CATEGORIES.map(c => html` `)} ${entries.length} entries
${loading ? html`
Loading…
` : html`<${EntryList} entries=${entries} onDelete=${deleteEntry} />` }
`; } function EntryForm({ onSubmit }) { var [message, setMessage] = useState(''); var [category, setCategory] = useState('note'); var [submitting, setSubmitting] = useState(false); async function submit(e) { e.preventDefault(); if (!message.trim()) return; setSubmitting(true); await onSubmit(category, message.trim()); setMessage(''); setSubmitting(false); } return html`
setMessage(e.target.value)} disabled=${submitting} />
`; } function EntryList({ entries, onDelete }) { if (!entries.length) { return html`
No entries yet. Log your first activity above.
`; } return html`
${entries.map(e => html`
${esc(e.username || 'unknown')} ${timeAgo(e.created_at)}
${esc(e.message)}
`)}
`; } // ── Utilities ────────────────────────────── function esc(s) { var el = document.createElement('span'); el.textContent = String(s || ''); return el.innerHTML; } function timeAgo(iso) { if (!iso) return ''; var ms = Date.now() - new Date(iso).getTime(); var sec = Math.floor(ms / 1000); if (sec < 60) return 'just now'; var min = Math.floor(sec / 60); if (min < 60) return min + 'm ago'; var hr = Math.floor(min / 60); if (hr < 24) return hr + 'h ago'; var d = Math.floor(hr / 24); return d + 'd ago'; } // ── Mount ────────────────────────────────── render(html`<${App} />`, mount); console.log('[team-activity-log] Surface mounted'); })();