// Topbar: brand, global search (Enter → /search?q=), capture button stub, // pending bell with badge, user/agent toggle stub. import { el, mount, clear } from '../dom.js'; import { navigate } from '../router.js'; import { on } from '../state.js'; import { toggleSidebar } from './chrome.js'; import { api } from '../api.js'; // Cluster health → topbar pill. Returns [status, label, title]. function classifyCluster(c) { if (!c || c.error) return ['unknown', 'cluster ?', 'Cluster status unavailable']; if (!c.quorate) return ['down', 'no quorum', 'Cluster has LOST quorum']; if ((c.nodes_online ?? 0) < (c.nodes_total ?? 0)) return ['down', 'node down', `${c.nodes_online}/${c.nodes_total} nodes online`]; if (c.ha && c.ha.services_error > 0) return ['warn', 'HA issue', `${c.ha.services_error} HA service(s) in error`]; return ['ok', 'healthy', `Quorate · ${c.nodes_online}/${c.nodes_total} nodes · HA ok`]; } function startClusterHealth(pill, labelEl) { async function tick() { let c = null; try { c = await api.get('/api/cluster'); } catch { c = { error: 'fetch' }; } const [status, label, title] = classifyCluster(c); pill.className = 'icon-btn cluster-health status-' + status; pill.title = title; labelEl.textContent = label; } tick(); setInterval(tick, 30000); } function captureModal() { const root = document.getElementById('modal-root'); mount(root, el('div', { class: 'modal-backdrop', onclick: (e) => { if (e.target.classList.contains('modal-backdrop')) clear(root); } }, el('div', { class: 'modal' }, el('h2', {}, 'Universal Capture'), el('p', { class: 'muted' }, 'Drag a URL, paste a YouTube link, drop a PDF. ' + 'Plan 3 wires the capture queue + workers — this surface is here so the UX shape is honest about what is coming.' ), el('div', { class: 'actions' }, el('button', { class: 'ghost', onclick: () => clear(root) }, 'Close') ) ) ) ); } export function renderTopbar(root) { const searchInput = el('input', { type: 'text', placeholder: 'Search … (Enter to search)', onkeydown: (e) => { if (e.key === 'Enter' && e.target.value.trim()) { navigate('/search?q=' + encodeURIComponent(e.target.value.trim())); } } }); const bell = el('button', { class: 'icon-btn', onclick: () => navigate('/inbox') }, 'Inbox'); const chLabel = el('span', { class: 'ch-label' }, '…'); const clusterPill = el('button', { class: 'icon-btn cluster-health status-unknown', title: 'Cluster health', onclick: () => navigate('/sacred-valley') }, el('span', { class: 'dot' }), chLabel); mount(root, el('button', { class: 'chrome-toggle', title: 'Toggle menu', onclick: toggleSidebar }, '☰'), el('div', { class: 'brand' }, 'VOID'), el('button', { class: 'icon-btn', onclick: captureModal }, '+ Capture'), el('div', { class: 'topbar-search' }, searchInput), el('div', { class: 'topbar-spacer' }), clusterPill, bell, el('button', { class: 'chrome-toggle', title: 'Summon Dross', onclick: () => window.dispatchEvent(new CustomEvent('dross-toggle')) }, '◆'), el('button', { class: 'icon-btn', onclick: () => alert('Agent-switching ships post-Plan-2.') }, 'Owner') ); startClusterHealth(clusterPill, chLabel); on('pending-count', (n) => { const old = bell.querySelector('.badge'); if (old) old.remove(); if (n > 0) bell.appendChild(el('span', { class: 'badge' }, String(n))); }); }