Spaces:
Sleeping
Sleeping
| /** | |
| * 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<ReturnType<typeof setTimeout> | 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<AgentFileChangedDetail>).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 | |
| } | |