// — content rendered inside for the todos // flyout. "mark done" POSTs to this agent's own backend // (`api/todos/mark-done`, same-origin), unlike InboxPanel's cross-agent // mark-all-read. import { useState } from "preact/hooks"; import type { TodoRow } from "../types.js"; export interface TodosPanelProps { todos: TodoRow[]; onCleared: () => void; } function fmtAge(seconds: number): string { if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; return `${Math.floor(seconds / 86400)}d`; } export function TodosPanel({ todos, onCleared }: TodosPanelProps) { const [checked, setChecked] = useState>(new Set()); const [status, setStatus] = useState(""); const [busy, setBusy] = useState(false); function toggle(id: number) { setChecked((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } async function markDone() { const ids = [...checked]; if (!ids.length) return; setBusy(true); setStatus("marking…"); try { const resp = await fetch("api/todos/mark-done", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: `ids=${encodeURIComponent(ids.join(","))}`, }); if (resp.ok) { setStatus("✓ marked done"); setChecked(new Set()); onCleared(); } else { setStatus(`failed: ${await resp.text()}`); } } catch (err) { setStatus(`failed: ${err instanceof Error ? err.message : String(err)}`); } finally { setBusy(false); } } if (!todos.length) { return (

no todos — all subsystem queues are clear.

); } const allChecked = todos.length > 0 && todos.every((t) => checked.has(t.id)); return (
{status}
    {todos.map((t) => { const cbId = `todo-cb-${t.id}`; const label = t.source ? `${t.subsystem} · ${t.source}` : t.subsystem; return (
  • toggle(t.id)} />{" "} {" "} {fmtAge(t.age_seconds)} ago
    {t.summary}
  • ); })}
); }