// §15.3 — the toast surface. // // Toasts fire for the user's own actions completing and for events // landing on the exact view the user is currently looking at. The // inbox row still lands for the same event; the toast just carries // the "something just happened here" beat per §15.3. // // This component is a simple stack with a small cap (4 visible at // once). Newer toasts queue behind the visible ones rather than // stacking endlessly. Auto-dismiss after a short interval; click to // dismiss early. import { useEffect, useState } from 'react' const MAX_VISIBLE = 4 const AUTO_DISMISS_MS = 6000 let _emit = null export function showToast({ summary, category, link }) { if (_emit) _emit({ id: Math.random().toString(36).slice(2), summary, category, link, ts: Date.now() }) } export default function ToastHost() { const [toasts, setToasts] = useState([]) useEffect(() => { _emit = (t) => setToasts(prev => [...prev, t]) return () => { _emit = null } }, []) useEffect(() => { if (toasts.length === 0) return const t = setTimeout(() => { setToasts(prev => prev.slice(1)) }, AUTO_DISMISS_MS) return () => clearTimeout(t) }, [toasts]) const visible = toasts.slice(0, MAX_VISIBLE) return (
{visible.map(t => (
setToasts(prev => prev.filter(x => x.id !== t.id))} > {t.summary}
))}
) }