// Command palette overlay const Palette = ({ onClose }) => { const { go } = useRouter(); const [q, setQ] = React.useState(''); const [sel, setSel] = React.useState(0); const inputRef = React.useRef(null); React.useEffect(() => { inputRef.current && inputRef.current.focus(); }, []); React.useEffect(() => { setSel(0); }, [q]); const jumps = [ { kind: 'jump', label: 'Recent', hint: 'g h', go: { name: 'home' } }, { kind: 'jump', label: 'Projects', hint: 'g p', go: { name: 'projects' } }, { kind: 'jump', label: 'Stats', hint: 'g s', go: { name: 'stats' } }, { kind: 'jump', label: 'Health', hint: 'g i', go: { name: 'health' } }, { kind: 'jump', label: 'Settings', hint: '', go: { name: 'settings' } }, ]; const sessionItems = SESSIONS.map(s => ({ kind: 'session', label: s.q, hint: `${s.tool} · ${s.cwd}`, go: { name: 'session', id: s.id } })); const projectItems = PROJECTS.map(p => ({ kind: 'project', label: p.cwd, hint: `${p.sessions} sessions`, go: { name: 'project', cwd: p.cwd } })); const all = [...jumps, ...projectItems, ...sessionItems]; const filtered = q.trim() === '' ? all : all.filter(x => (x.label + ' ' + x.hint).toLowerCase().includes(q.toLowerCase()) ); const showSearch = q.trim() !== ''; const total = filtered.length + (showSearch ? 1 : 0); const fire = (idx) => { if (showSearch && idx === 0) { go({ name: 'search', q }); onClose(); return; } const item = filtered[showSearch ? idx - 1 : idx]; if (item) { go(item.go); onClose(); } }; const onKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } else if (e.key === 'ArrowDown') { e.preventDefault(); setSel(s => Math.min(total - 1, s + 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSel(s => Math.max(0, s - 1)); } else if (e.key === 'Enter') { e.preventDefault(); fire(sel); } }; return (
e.stopPropagation()}>
⌘K setQ(e.target.value)} onKeyDown={onKey} placeholder="search turns, jump to a page, pick a session…" />
{showSearch && (
fire(0)}> search "{q}"
)} {filtered.map((item, i) => { const idx = showSearch ? i + 1 : i; const active = idx === sel; return (
fire(idx)}> {item.kind} {item.label} {item.hint}
); })} {filtered.length === 0 && !showSearch && (
no matches
)}
↑↓ move↵ openesc close g h · g p · g s · g i
); }; Object.assign(window, { Palette });