AUDIT / src /hooks /useAgentFileChanged.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
2.74 kB
/**
* 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
}