"use client"; // Settings panel for the desktop shell's encrypted local search index. // // Deliberately small: the index's PRIMARY trigger is the live push connection // (see lib/mail-index-client.ts's indexOnStateChange, wired into // stores/email-store.ts's handleStateChange), so this panel is a status readout // plus a manual catch-up button - not the mechanism. // // Renders nothing at all outside the Electron shell, where the routes 404. import { useCallback, useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { SettingsSection, SettingItem } from './settings-section'; import { isElectronShell } from '@/lib/electron-bridge'; import { useAccountStore } from '@/stores/account-store'; import { catchUpIndex, fetchIndexStats, type IndexStats } from '@/lib/mail-index-client'; const TYPE_LABELS: Record = { mail: 'Mail', calendar: 'Calendar', contact: 'Contacts', file: 'Files', }; export function LocalIndexSettings() { const slot = useAccountStore((s) => s.accounts.find((a) => a.id === s.activeAccountId)?.cookieSlot); const [stats, setStats] = useState(null); const [busy, setBusy] = useState(false); const [message, setMessage] = useState(null); // `null` until the first probe resolves, so we don't flash a panel that then // vanishes on a non-desktop build. const [available, setAvailable] = useState(null); const refreshStats = useCallback(async () => { const next = await fetchIndexStats(slot); setStats(next); setAvailable(next !== null); }, [slot]); useEffect(() => { if (!isElectronShell()) { setAvailable(false); return; } void refreshStats(); }, [refreshStats]); const handleRebuild = async () => { setBusy(true); setMessage(null); try { const result = await catchUpIndex(slot); if (result.unavailable) { setAvailable(false); setMessage(result.error ?? 'The encrypted index is unavailable on this system.'); return; } if (!result.ok) { setMessage(result.error ?? 'Indexing failed.'); return; } const written = Object.entries(result.written ?? {}) .map(([type, n]) => `${TYPE_LABELS[type] ?? type}: ${n}`) .join(', '); const failed = (result.errors ?? []).map((e) => `${e.contentType} (${e.message})`).join('; '); setMessage( [ written ? `Indexed ${written}.` : 'Nothing to index.', result.skipped?.length ? `Not supported: ${result.skipped.join(', ')}.` : '', failed ? `Problems: ${failed}` : '', ] .filter(Boolean) .join(' '), ); await refreshStats(); } finally { setBusy(false); } }; if (available === false || available === null) return null; const total = (stats ?? []).reduce((sum, s) => sum + s.count, 0); return ( 0 ? (stats ?? []) .map((s) => `${TYPE_LABELS[s.contentType] ?? s.contentType}: ${s.count}`) .join(' · ') : 'Nothing indexed yet.' } > {total} ); }