/** * useBootEffects.ts — S-BE1: Tutti gli effetti di bootstrap dell'app * * Estratto da MainWorkspace.tsx (S-BE1): * - hydrateKnowledgeFromCloud (cloud context) * - prewarmProviders (TCP/TLS pre-connect S316) * - autoRecoverTasks (S696 task recovery) * - agentLoop chunk preload (S439 requestIdleCallback) * - agent:stream-chunk SSE listener (S422) * - useStorageAutoPrune / useSafariLifecycle / useSafariMemory * - selfDiag (autodiagnosi critica) * - VFS → previewHtml + setTab("preview") * - agente:preview_open event listener * - sessionStorage cleanup on unload * - ghListCommits fire-and-forget * * Nessun parametro, nessun valore di ritorno — puri side-effects. * Usa getState() per setPreviewHtml/setActiveTab → zero stale-closure deps. */ import { useEffect } from "react"; import { useStorageAutoPrune } from "@/hooks/useStorageQuota"; import { useSafariLifecycle } from "@/hooks/useSafariLifecycle"; import { useSafariMemory } from "@/hooks/useSafariMemory"; import { hydrateKnowledgeFromCloud } from "@/lib/contextBridge"; import { prewarmProviders } from "@/lib/providerChain"; import { ghListCommits, getRepoConfig } from "@/lib/github"; import { vfsAsync, onVfsChanged } from "@/lib/vfsDb"; import { selfDiag } from "@/lib/selfDiag"; import { type Message } from "@/lib/storage"; import { useConversationStore } from "@/store/conversationStore"; import { useWorkspaceStore } from "@/store/workspaceStore"; import { useUIStore } from "@/store/uiStore"; import { useChatStore } from "@/store/chatStore"; // ── agentLoop lazy cache (modulo-level, condiviso con gli effetti del hook) ── let _agentLoopPromise: Promise | null = null; const _loadAgentLoop = (): Promise => (_agentLoopPromise ??= import("@/lib/agentLoop")); // ───────────────────────────────────────────────────────────────────────────── export function useBootEffects(): void { // ── Cloud context hydration ─────────────────────────────────────────────── useEffect(() => { hydrateKnowledgeFromCloud(); }, []); // ── S316: prewarm TCP/TLS verso Groq — riduce cold-start ~300ms su mobile/Safari useEffect(() => { prewarmProviders(); }, []); // ── S696: auto-recovery — drena task interrotti < 10min fa (max 3 task) ── useEffect(() => { import("@/lib/taskRecovery").then(m => m.autoRecoverTasks()).catch(() => {}); }, []); // eslint-disable-line react-hooks/exhaustive-deps // ── S439: preload agentLoop chunk via requestIdleCallback ───────────────── // Garantisce chunk in memoria prima del primo invio messaggio. // Fallback setTimeout 2s se rIC non supportato. useEffect(() => { const load = () => { _loadAgentLoop().catch(() => {}); }; if (typeof requestIdleCallback !== "undefined") { const id = requestIdleCallback(load, { timeout: 3_000 }); return () => cancelIdleCallback(id); } const t = setTimeout(load, 2_000); return () => clearTimeout(t); }, []); // ── S422: SSE token listener → chatStore (live stream senza re-render completo) useEffect(() => { const handler = (e: Event) => { const ev = e as CustomEvent<{ taskId: string; token: string }>; useChatStore.getState().appendStreamChunk(ev.detail.taskId, ev.detail.token); }; window.addEventListener("agent:stream-chunk", handler); return () => window.removeEventListener("agent:stream-chunk", handler); }, []); // ── Storage / Safari lifecycle hooks ───────────────────────────────────── useStorageAutoPrune(); useSafariLifecycle(); useSafariMemory(); // ── selfDiag — inietta messaggio critico in conversazione attiva ────────── useEffect(() => { selfDiag.start(); return selfDiag.onCritical(report => { const sysMsg: Message = { id: crypto.randomUUID(), role: "assistant", content: `🔍 **Autodiagnosi:** ${report}`, createdAt: Date.now(), }; useConversationStore.getState().setMessages(prev => [...prev, sysMsg]); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // ── VFS → previewHtml: apre tab preview quando VFS produce un .html ─────── // Usa getState() — i setter Zustand sono stabili, zero stale-closure deps. useEffect(() => { const check = () => { vfsAsync.list() .then(files => { const html = files .filter(f => f.name.endsWith(".html") || f.path.endsWith(".html")) .sort((a, b) => b.updatedAt - a.updatedAt)[0]; if (html?.content) { useWorkspaceStore.getState().setPreviewHtml({ content: html.content, name: html.name }); useUIStore.getState().setActiveTab("preview"); } }) .catch(() => {}); }; return onVfsChanged(check); }, []); // ── agente:preview_open — preview HTML inline da tool output ───────────── useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent<{ path?: string; html?: string; name?: string }>).detail; if (detail?.html) { const fname = detail.name ?? detail.path?.split("/").pop() ?? "preview.html"; useWorkspaceStore.getState().setPreviewHtml({ content: detail.html, name: fname }); useUIStore.getState().setActiveTab("preview"); } }; window.addEventListener("agente:preview_open", handler); return () => window.removeEventListener("agente:preview_open", handler); }, []); // ── sessionStorage cleanup on unload ───────────────────────────────────── useEffect(() => { const cleanup = () => { try { sessionStorage.removeItem("agente_active_id"); } catch { /**/ } }; window.addEventListener("beforeunload", cleanup); return () => window.removeEventListener("beforeunload", cleanup); }, []); // ── GH recent commit (fire-and-forget) ─────────────────────────────────── useEffect(() => { const { owner, repo } = getRepoConfig(); ghListCommits(owner, repo, 1).catch(() => {}); }, []); }