/** * useAgentFileChanged.ts — Fix D: watch mode preview signal listener * * Ascolta il CustomEvent "agent:file-changed" emesso da toolExecutor.ts * ogni volta che apply_patch / write_file / create_file / iterate_file * completano con successo (tool non-errore). * * Design: * - Safari-safe: CustomEvent + addEventListener (nessun BroadcastChannel) * - Zero-cost se enabled=false: nessun listener registrato * - Debounce: batch di scritture rapide consecutive → un solo callback * - Stable callback ref: il callback può aggiornarsi senza re-subscribe * * Emitter (toolExecutor.ts Fix D): * window.dispatchEvent(new CustomEvent("agent:file-changed", { detail: { tool, t } })) * * @module useAgentFileChanged */ import { useEffect, useRef } from "react"; export interface AgentFileChangedDetail { /** Nome del tool che ha modificato il file (apply_patch, write_file, …) */ tool: string; /** Timestamp Unix ms dell'evento */ t: number; } /** * Registra un listener per "agent:file-changed" con debounce. * * @param onChanged — callback stabile: chiamata al massimo ogni `debounceMs` * @param debounceMs — debounce tra eventi ravvicinati (default 300ms) * @param enabled — false = nessun listener (default true) * * @example * // In PreviewSandbox: refresh automatico quando l'agente scrive un file * useAgentFileChanged( * useCallback(() => { if (html) runtime.refresh("auto"); }, [html, runtime]), * ); */ export function useAgentFileChanged( onChanged: (detail: AgentFileChangedDetail) => void, debounceMs = 300, enabled = true, ): void { const timerRef = useRef | null>(null); // Stable callback ref — aggiornato ogni render senza rifare il subscribe const callbackRef = useRef(onChanged); useEffect(() => { callbackRef.current = onChanged; }); useEffect(() => { if (!enabled || typeof window === "undefined") return; const handler = (e: Event) => { const detail = (e as CustomEvent).detail ?? { tool: "unknown", t: Date.now() }; // Debounce: cancella il timer precedente e rischedula if (timerRef.current !== null) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { timerRef.current = null; callbackRef.current(detail); }, debounceMs); }; window.addEventListener("agent:file-changed", handler); return () => { window.removeEventListener("agent:file-changed", handler); if (timerRef.current !== null) { clearTimeout(timerRef.current); timerRef.current = null; } }; }, [enabled, debounceMs]); // callbackRef è stabile — non serve in deps }