feat(ui): sidebar + topbar + rightrail components

Sidebar: Spaces tree with lazy-expand to projects on caret click; bottom
Navigate section with Sacred Valley / Search / Inbox + placeholders for
Agents and Resources greyed out as later. Inbox item carries a
pending-count badge that wires to state.js so the topbar bell and the
sidebar share one poll.

Topbar: brand, + Capture button (modal stub for Plan 3 capture queue),
global search input (Enter -> /search?q=), pending Inbox bell with
matching badge, Owner toggle (stub for agent-switching post-Plan-2).

Rightrail remains the T17 collapsible companion placeholder.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-01 02:14:44 +10:00
parent 59ad86425d
commit e5b2181ee3
2 changed files with 148 additions and 15 deletions

View File

@@ -1,20 +1,53 @@
// T17 stub — full implementation lands in T18.
import { el, mount } from '../dom.js';
// 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';
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 search = el('input', {
const searchInput = el('input', {
type: 'text',
placeholder: 'Search …',
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');
mount(root,
el('div', { class: 'brand' }, 'VOID'),
el('div', { class: 'topbar-search' }, search),
el('div', { class: 'topbar-spacer' })
el('button', { class: 'icon-btn', onclick: captureModal }, '+ Capture'),
el('div', { class: 'topbar-search' }, searchInput),
el('div', { class: 'topbar-spacer' }),
bell,
el('button', { class: 'icon-btn', onclick: () => alert('Agent-switching ships post-Plan-2.') }, 'Owner')
);
on('pending-count', (n) => {
const old = bell.querySelector('.badge');
if (old) old.remove();
if (n > 0) bell.appendChild(el('span', { class: 'badge' }, String(n)));
});
}