/** * View-model hook for the project work-state read-model. * * It consumes the WorkStateGateway only, and optionally subscribes to existing * domain events through SystemGateway to refresh the read-only snapshot. */ import { useCallback, useEffect, useState } from "react"; import type { GatewayError, ProjectWorkState } from "@/domain"; import { useGateways } from "@/app/di"; export interface ProjectWorkStateViewModel { state: ProjectWorkState | null; error: string | null; busy: boolean; refresh: () => 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 useProjectWorkState(projectId: string): ProjectWorkStateViewModel { const { workState, system } = useGateways(); const [state, setState] = useState(null); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const refresh = useCallback(async () => { setBusy(true); setError(null); try { setState(await workState.getProjectWorkState(projectId)); } catch (e) { setError(describe(e)); } finally { setBusy(false); } }, [projectId, workState]); useEffect(() => { void refresh(); }, [refresh]); useEffect(() => { if (!system) return; let unsubscribe: (() => void) | undefined; let cancelled = false; void system.onDomainEvent((event) => { if ( event.type === "agentLaunched" || event.type === "agentExited" || event.type === "agentBusyChanged" || event.type === "delegationReady" || event.type === "orchestratorRequestProcessed" || event.type === "backgroundTaskChanged" || event.type === "agentInboxChanged" || event.type === "agentWakeChanged" ) { void refresh(); } }).then((u) => { if (cancelled) u(); else unsubscribe = u; }); return () => { cancelled = true; unsubscribe?.(); }; }, [refresh, system]); return { state, error, busy, refresh }; }