'use client'; import { useCallback, useRef, useState } from 'react'; // ISS-008 (v0.17.0): transient toast notifications — a bottom-right stack for action errors // and game events derived from snapshot diffs. Each toast auto-dismisses after TOAST_TTL_MS; // the queue is capped at MAX_TOASTS (oldest dropped) so an event burst can't fill the corner. export type ToastKind = 'error' | 'info'; export interface Toast { id: number; kind: ToastKind; message: string; } export const TOAST_TTL_MS = 5_000; export const MAX_TOASTS = 4; // Pure core (unit-tested): append a toast, dropping the oldest beyond the cap. export function pushToast(toasts: Toast[], toast: Toast): Toast[] { return [...toasts, toast].slice(-MAX_TOASTS); } export interface UseToastsResult { toasts: Toast[]; push: (kind: ToastKind, message: string) => void; dismiss: (id: number) => void; } export function useToasts(): UseToastsResult { const [toasts, setToasts] = useState([]); const nextIdRef = useRef(1); // Functional update — a late timer for an already-dismissed/dropped id is a no-op. const dismiss = useCallback((id: number) => { setToasts((current) => current.filter((t) => t.id !== id)); }, []); const push = useCallback( (kind: ToastKind, message: string) => { const toast: Toast = { id: nextIdRef.current++, kind, message }; setToasts((current) => pushToast(current, toast)); setTimeout(() => dismiss(toast.id), TOAST_TTL_MS); }, [dismiss], ); return { toasts, push, dismiss }; }