import { useEffect } from 'react'; import { Outlet } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { useIdleAutoLock } from '../hooks/useIdleAutoLock'; import { startConversationKeySync } from '../lib/conversationKeySync'; import { startDeviceApprovalListener } from '../lib/deviceApproval'; import { ensureInstallId } from '../lib/installId'; import { ensureNotificationPermission } from '../lib/osNotify'; import { useMentionNotifications } from '../lib/useMentionNotifications'; import { cachedUserKey } from '../lib/userIdentity'; import { CallUI } from './CallUI'; import { DeviceApprovalBanner } from './DeviceApprovalBanner'; import { Sidebar } from './Sidebar'; export function AppShell() { const { session } = useAuth(); useIdleAutoLock(); useMentionNotifications(session?.user.id); useEffect(() => { // Prompt once per authenticated shell mount. Module-level guard prevents // re-asking if the user already responded this session. void ensureNotificationPermission(); }, []); useEffect(() => { if (!session?.user.id) return; const userId = session.user.id; // Per-install id replaces the per-device record now that crypto is // user-keyed. The legacy conv-key-sync and device-approval subsystems // still take a "deviceId" argument for telemetry / row identity; passing // the install id keeps those wires intact until they are themselves // reworked in later tasks (Task 16/17/19 territory). const installId = ensureInstallId(); const stopKeySync = startConversationKeySync(userId, installId); const stopApproval = startDeviceApprovalListener({ ownUserId: userId, ownDeviceId: installId, getPriv: () => cachedUserKey(userId), }); return () => { stopKeySync(); stopApproval(); }; }, [session?.user.id]); return (
); } // Subtle desktop texture for dark mode. The panels carry the depth; the // background stays quiet so chat content remains the focus. function ShellBackground() { return (