+
+
Waiting for commands...
+
Run a browse command to see activity here.
+
+
+
+
+
+
+
+
No refs yet
+
Run snapshot to see element refs.
+
+
+
+
+
+
+
+
+
Full session view requires Conductor.
+
Activity tab shows browse commands.
+
+
+
+
+
+
${escapeHtml(entry.result)}
+
+ ` : ''}
+ `;
+
+ // Click to expand/collapse
+ div.addEventListener('click', () => div.classList.toggle('expanded'));
+ div.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') div.classList.toggle('expanded');
+ if (e.key === 'Escape') div.classList.remove('expanded');
+ });
+
+ // Screen reader label
+ const srLabel = `${entry.command || entry.type} ${argsText} ${statusIcon ? (entry.status === 'ok' ? 'succeeded' : 'failed') : 'in progress'} ${duration ? 'in ' + duration : ''}`;
+ div.setAttribute('aria-label', srLabel);
+
+ return div;
+}
+
+function addEntry(entry) {
+ const feed = document.getElementById('activity-feed');
+ const empty = document.getElementById('empty-state');
+ if (empty) empty.style.display = 'none';
+
+ // If command_end, update the matching pending entry
+ if (entry.type === 'command_end') {
+ // Remove the pending command_start for this command
+ for (const [id, el] of pendingEntries) {
+ if (el.querySelector('.entry-command')?.textContent === entry.command) {
+ el.remove();
+ pendingEntries.delete(id);
+ break;
+ }
+ }
+ }
+
+ const el = createEntryElement(entry);
+ feed.appendChild(el);
+
+ if (entry.type === 'command_start') {
+ pendingEntries.set(entry.id, el);
+ }
+
+ // Auto-scroll
+ el.scrollIntoView({ behavior: 'smooth', block: 'end' });
+
+ // Update footer
+ if (entry.url) document.getElementById('footer-url').textContent = new URL(entry.url).hostname;
+ const parts = [];
+ if (entry.tabs) parts.push(`${entry.tabs} tabs`);
+ if (entry.mode) parts.push(entry.mode);
+ if (parts.length) document.getElementById('footer-info').textContent = parts.join(' \u00b7 ');
+
+ lastId = Math.max(lastId, entry.id);
+}
+
+function escapeHtml(str) {
+ const div = document.createElement('div');
+ div.textContent = str;
+ return div.innerHTML;
+}
+
+// ─── SSE Connection ────────────────────────────────────────────
+
+function connectSSE() {
+ if (!serverUrl) return;
+
+ if (eventSource) {
+ eventSource.close();
+ eventSource = null;
+ }
+
+ const url = `${serverUrl}/activity/stream?after=${lastId}`;
+ eventSource = new EventSource(url);
+
+ eventSource.addEventListener('activity', (e) => {
+ try {
+ const entry = JSON.parse(e.data);
+ addEntry(entry);
+ } catch {}
+ });
+
+ eventSource.addEventListener('gap', (e) => {
+ try {
+ const data = JSON.parse(e.data);
+ const feed = document.getElementById('activity-feed');
+ const banner = document.createElement('div');
+ banner.className = 'gap-banner';
+ banner.textContent = `Missed ${data.availableFrom - data.gapFrom} events (buffer overflow)`;
+ feed.appendChild(banner);
+ } catch {}
+ });
+
+ eventSource.onerror = () => {
+ // EventSource auto-reconnects
+ };
+}
+
+// ─── Refs Tab ──────────────────────────────────────────────────
+
+async function fetchRefs() {
+ if (!serverUrl) return;
+ try {
+ const resp = await fetch(`${serverUrl}/refs`, { signal: AbortSignal.timeout(3000) });
+ if (!resp.ok) return;
+ const data = await resp.json();
+
+ const list = document.getElementById('refs-list');
+ const empty = document.getElementById('refs-empty');
+ const footer = document.getElementById('refs-footer');
+
+ if (!data.refs || data.refs.length === 0) {
+ empty.style.display = '';
+ list.innerHTML = '';
+ footer.textContent = '';
+ return;
+ }
+
+ empty.style.display = 'none';
+ list.innerHTML = data.refs.map(r => `
+