// useTodos — polls `GET /api/todos` on a light interval, same cadence // as app.js's `setInterval(refreshTodos, 4000)`. Kept separate from // useAgentState: todos change asynchronously (matrix syncs, bash task // starts/completions) independent of the state snapshot, and the old // page polled them independently too. import { useEffect, useRef, useState } from "preact/hooks"; import type { TodoRow } from "../types.js"; const POLL_MS = 4000; export interface UseTodosResult { todos: TodoRow[]; refresh: () => void; } export function useTodos(): UseTodosResult { const [todos, setTodos] = useState([]); const timerRef = useRef | null>(null); const stoppedRef = useRef(false); async function poll() { try { const resp = await fetch("api/todos"); if (!resp.ok) throw new Error(`http ${resp.status}`); const body = (await resp.json()) as { todos?: TodoRow[] }; if (stoppedRef.current) return; // Defensive filter: the wire type is the general `LooseEnd` tagged // enum even though this endpoint only ever emits the `todo` variant // today — see types.ts's TodoRow comment. setTodos((body.todos ?? []).filter((t) => t.kind === "todo")); } catch (err) { console.warn("todos fetch failed", err); if (!stoppedRef.current) setTodos([]); } } useEffect(() => { stoppedRef.current = false; poll(); timerRef.current = setInterval(poll, POLL_MS); return () => { stoppedRef.current = true; if (timerRef.current) clearInterval(timerRef.current); }; }, []); return { todos, refresh: poll }; }