const { useState, useEffect, useRef, useMemo, useCallback } = React; function App() { // ── Theme (dark mode lives in localStorage) ──────────────────────── const [dark, setDark] = useState(() => { try { return localStorage.getItem('relay.dark') === '1'; } catch { return false; } }); useEffect(() => { document.body.classList.toggle('dark', dark); try { localStorage.setItem('relay.dark', dark ? '1' : '0'); } catch {} }, [dark]); // ── App state ────────────────────────────────────────────────────── const [chats, setChats] = useState([]); const [selectedId, setSelectedId] = useState(null); const [tab, setTab] = useState('chats'); const [bridgeState, setBridgeState] = useState('disconnected'); const [bridgeIp, setBridgeIp] = useState(null); const [log, setLog] = useState([]); const [responses, setResponses] = useState([]); const [contactModal, setContactModal] = useState(null); // { mode, initial } | null const bridgeRef = useRef(null); // Build the bridge once and wire SSE callbacks into state setters. if (!bridgeRef.current) { bridgeRef.current = createBridge({ onLog: (entry) => setLog(prev => trimTail([...prev, entry], 1000)), onState: ({ state, ip }) => { setBridgeState(state); setBridgeIp(ip); }, onResp: (resp) => setResponses(prev => trimTail([...prev, resp], 80)), onReady: (boot) => { setBridgeState(boot.state.state); setBridgeIp(boot.state.ip); setChats(boot.chats); setResponses(boot.responses); setLog(boot.log); }, onInboundSMS: ({ chat, message }) => setChats(prev => mergeMessage(prev, chat, message)), onInboundCall: ({ chat, message }) => setChats(prev => mergeMessage(prev, chat, message)), onSmsOut: ({ chat, message }) => setChats(prev => mergeMessage(prev, chat, message)), onSmsOutStatus: ({ chat_number, message_id, status }) => { setChats(prev => prev.map(c => c.number === chat_number ? { ...c, messages: c.messages.map(m => m.id === message_id ? { ...m, status } : m) } : c)); }, onChatCreated: (chat) => setChats(prev => mergeChat(prev, chat)), onCleared: () => { setChats([]); setResponses([]); setLog([]); }, }); } const bridge = bridgeRef.current; // Bootstrap on mount. useEffect(() => { bridge.bootstrap().catch(console.error); const es = bridge.startStream(); return () => es.close(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const sortedChats = useMemo(() => sortChats(chats), [chats]); const selectedChat = useMemo( () => chats.find(c => c.number === selectedId) || null, [chats, selectedId] ); const handleSelect = (number) => { setSelectedId(number); setTab('chats'); }; const handleSend = async (chatNumber, body) => { // Server inserts the message + broadcasts sms_out via SSE — no local optimistic insert here. await bridge.sendSMS(chatNumber, body); }; const handleSaveContact = async (number, name) => { const chat = await bridge.createChat(number, name); // upsert setChats(prev => mergeChat(prev, chat)); setSelectedId(chat.number); setContactModal(null); setTab('chats'); }; const settingsBadge = useMemo(() => { if (bridgeState === 'disconnected' || bridgeState === 'bridge_offline') return '!'; return 0; }, [bridgeState]); return (