// Vault-tab sidebar column: its static markup, the category nav rendering, // nav-button wiring, and the (now debounced) search input. Each function // receives the VaultController (`ctx`) and reaches sibling concerns through it; // pure helpers come from vault-context. Imports only from shared/ and // vault-context, plus the leaf renderer vault-status — never from vault-shell // or vault.ts. import type { ItemType } from '../shared/types'; import { GLYPH_TRASH, GLYPH_DEVICES, GLYPH_SETTINGS, GLYPH_HISTORY, GLYPH_LOCK, GLYPH_REFRESH, } from '../shared/glyphs'; import { renderStatusIndicator, type VaultStatus } from './vault-status'; import { type VaultController, typeIcon, typeLabel, getFilteredEntries, } from './vault-context'; const SEARCH_DEBOUNCE_MS = 80; // --------------------------------------------------------------------------- // Sidebar markup // --------------------------------------------------------------------------- export function renderSidebarShell(): string { return `
`; } // --------------------------------------------------------------------------- // Sidebar wiring // --------------------------------------------------------------------------- export function wireSidebar(ctx: VaultController): void { // Search (debounced — trailing edge) const searchInput = document.getElementById('vault-search') as HTMLInputElement | null; let searchTimer: number | undefined; searchInput?.addEventListener('input', () => { if (searchTimer !== undefined) clearTimeout(searchTimer); searchTimer = window.setTimeout(() => { ctx.state.searchQuery = searchInput.value; renderSidebarCategories(ctx); ctx.renderListPane(); }, SEARCH_DEBOUNCE_MS); }); // Nav buttons document.querySelectorAll('.vault-sidebar__nav-item').forEach((btn) => { btn.addEventListener('click', async () => { const nav = (btn as HTMLElement).dataset.nav; if (nav === 'lock') { await ctx.sendMessage({ type: 'lock' }); ctx.state.unlocked = false; ctx.state.selectedId = null; ctx.state.selectedItem = null; ctx.state.entries = []; ctx.render(); return; } if (nav === 'add') { ctx.state.selectedId = null; ctx.state.selectedItem = null; ctx.state.newType = null; ctx.state.drawerOpen = false; ctx.closeDrawer(); ctx.openTypePanel(); return; } if (nav === 'trash' || nav === 'devices' || nav === 'settings' || nav === 'history') { ctx.state.selectedId = null; ctx.state.selectedItem = null; ctx.state.newType = null; ctx.state.drawerOpen = false; ctx.state.view = nav; ctx.setHash(nav); ctx.applyShellViewClass(); ctx.renderPane(); return; } }); }); // Global "/" shortcut to focus search; Esc to close drawer document.addEventListener('keydown', (e) => { if (e.key === '/' && !isEditableTarget(e.target)) { e.preventDefault(); searchInput?.focus(); return; } if (e.key === 'Escape' && ctx.state.drawerOpen) { ctx.closeDrawer(); ctx.renderListPane(); } }); // Vault status indicator — refresh on mount + on the manual button only. // No timer polling: get_vault_status returns cached state and sync is // user-initiated (spec 2026-05-04, Phase 6). const refreshStatus = async (): Promise