// TaskList.tsx — left-rail task list. Polls listTasks every 5s, supports client-side search, // highlights the active task based on the current pathname. "use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { ensureDefaultProject, listTasks } from "@/lib/api"; import type { TaskRecord } from "@/lib/types"; function formatRelative(ts: number): string { const diff = Math.max(0, Date.now() - ts); const s = Math.floor(diff / 1000); if (s < 60) return `${s}s ago`; const m = Math.floor(s / 60); if (m < 60) return `${m}m ago`; const h = Math.floor(m / 60); if (h < 24) return `${h}h ago`; const d = Math.floor(h / 24); return `${d}d ago`; } export default function TaskList() { const pathname = usePathname(); const [projectId, setProjectId] = useState(null); const [tasks, setTasks] = useState([]); const [query, setQuery] = useState(""); // Resolve default project once on mount. useEffect(() => { let cancelled = false; ensureDefaultProject() .then((p) => { if (!cancelled) setProjectId(p.id); }) .catch(() => { /* Swallow — empty state will render. */ }); return () => { cancelled = true; }; }, []); // Poll tasks every 5s while projectId is known and component is mounted. useEffect(() => { if (!projectId) return; let cancelled = false; const fetchTasks = async () => { try { const { tasks: next } = await listTasks(projectId); if (!cancelled) setTasks(next); } catch { /* Keep last-known list on transient failure. */ } }; fetchTasks(); const id = setInterval(fetchTasks, 5000); return () => { cancelled = true; clearInterval(id); }; }, [projectId]); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); const sorted = [...tasks].sort((a, b) => b.updatedAt - a.updatedAt); if (!q) return sorted; return sorted.filter((t) => t.title.toLowerCase().includes(q)); }, [tasks, query]); const activeId = useMemo(() => { const m = pathname?.match(/\/tasks\/([^/]+)/); return m ? m[1] : null; }, [pathname]); return (

Tasks

setQuery(e.target.value)} placeholder="Search tasks…" className="w-full rounded-lg border border-border bg-white px-3 py-1.5 text-xs text-foreground placeholder:text-muted-fg focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/20" />
{filtered.length === 0 ? (
{/* Beaker / flask illustration */}

No tasks yet — start a chat to create one.

) : (
    {filtered.map((t) => { const isActive = t.id === activeId; return (
  • {t.title} {formatRelative(t.updatedAt)}
  • ); })}
)}
); }