/** * `useTemplates` — view-model hook for the templates feature (L7). * * Owns the templates list and CRUD actions. Consumes {@link TemplateGateway} * exclusively; never touches `invoke()` or `@tauri-apps/api`, keeping the * component layer testable with mock gateways (ARCHITECTURE §1.3). */ import { useCallback, useEffect, useState } from "react"; import type { GatewayError, Template } from "@/domain"; import type { CreateTemplateInput } from "@/ports"; import { useGateways } from "@/app/di"; /** What the templates UI needs from this hook. */ export interface TemplatesViewModel { /** All templates. */ templates: Template[]; /** Last error message, or `null`. */ error: string | null; /** Whether a request is in flight. */ busy: boolean; /** Reloads the template list. */ refresh: () => Promise; /** Creates a new template and refreshes the list. */ createTemplate: (input: CreateTemplateInput) => Promise; /** Updates the content of an existing template. */ updateTemplate: (templateId: string, content: string) => Promise; /** Deletes a template by id. */ deleteTemplate: (templateId: string) => Promise; /** Creates an agent from a template in the given project. */ createAgentFromTemplate: ( projectId: string, templateId: string, opts?: { name?: string; synchronized?: boolean }, ) => Promise; } function describe(e: unknown): string { if (e && typeof e === "object" && "message" in e) { return String((e as GatewayError).message); } return String(e); } export function useTemplates(): TemplatesViewModel { const { template } = useGateways(); const [templates, setTemplates] = useState([]); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const refresh = useCallback(async () => { setBusy(true); setError(null); try { const list = await template.listTemplates(); setTemplates(list); } catch (e) { setError(describe(e)); } finally { setBusy(false); } }, [template]); useEffect(() => { void refresh(); }, [refresh]); const createTemplate = useCallback( async (input: CreateTemplateInput) => { setBusy(true); setError(null); try { const created = await template.createTemplate(input); setTemplates((prev) => [...prev, created]); } catch (e) { setError(describe(e)); } finally { setBusy(false); } }, [template], ); const updateTemplate = useCallback( async (templateId: string, content: string) => { setBusy(true); setError(null); try { const updated = await template.updateTemplate(templateId, content); setTemplates((prev) => prev.map((t) => (t.id === templateId ? updated : t)), ); } catch (e) { setError(describe(e)); } finally { setBusy(false); } }, [template], ); const deleteTemplate = useCallback( async (templateId: string) => { setBusy(true); setError(null); try { await template.deleteTemplate(templateId); setTemplates((prev) => prev.filter((t) => t.id !== templateId)); } catch (e) { setError(describe(e)); } finally { setBusy(false); } }, [template], ); const createAgentFromTemplate = useCallback( async ( projectId: string, templateId: string, opts?: { name?: string; synchronized?: boolean }, ) => { setBusy(true); setError(null); try { await template.createAgentFromTemplate(projectId, templateId, opts); } catch (e) { setError(describe(e)); } finally { setBusy(false); } }, [template], ); return { templates, error, busy, refresh, createTemplate, updateTemplate, deleteTemplate, createAgentFromTemplate, }; }