import { useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' import { api } from '../api/client' import type { Task, TaskState, Worker } from '../api/types' import { Chip, Dot, Empty, EndpointGap, M, Panel } from '../components/Primitives' import './Projects.css' /** Orchestra has no projects endpoint. The registry that defines a project — * repo, remote, quality gate, verification policy, machine affinity — lives in * the coordinator's config.jsonc and is never served over HTTP. What this * screen shows is derived from work that actually exists: the tasks in * /v1/ui/overview carry a project, and the workers in the federation registry * declare which projects they will accept. Everything else is a stated gap, * not a plausible-looking guess. */ const ATTENTION: TaskState[] = ['needs_attention', 'blocked', 'failed'] interface ProjectRow { id: string tasks: Task[] counts: Record attention: number active: number /** Newest capture/check the coordinator has for this project's sessions. * A task carries no updated_at, so this is the only honest recency signal * the overview projection actually contains. */ lastSeen?: string workers: Worker[] } function fmt(at?: string) { if (!at) return undefined const d = new Date(at) return Number.isNaN(d.getTime()) ? at : `${d.toISOString().slice(0, 16).replace('T', ' ')} UTC` } function newest(a?: string, b?: string) { if (!a) return b if (!b) return a return a > b ? a : b } function derive(tasks: Task[], workers: Worker[]): ProjectRow[] { const rows = new Map() for (const task of tasks) { const id = task.project || '(unset)' let row = rows.get(id) if (!row) { row = { id, tasks: [], counts: {} as Record, attention: 0, active: 0, workers: workers.filter((w) => (w.supported_projects || []).includes(id)), } rows.set(id, row) } row.tasks.push(task) row.counts[task.state] = (row.counts[task.state] || 0) + 1 if (ATTENTION.includes(task.state)) row.attention++ if (task.state === 'leased') row.active++ row.lastSeen = newest( row.lastSeen, newest(task.last_session?.captured_at, task.last_session?.checked_at), ) } return [...rows.values()].sort( (a, b) => b.attention - a.attention || b.active - a.active || a.id.localeCompare(b.id), ) } function stateChips(counts: Record) { const order: TaskState[] = [ 'leased', 'queued', 'in_review', 'needs_attention', 'blocked', 'failed', 'completed', ] return order .filter((state) => counts[state]) .map((state) => ( {state.replace('_', ' ')} {counts[state]} )) } export function Projects() { const overview = useQuery({ queryKey: ['overview'], queryFn: api.overview, refetchInterval: 5000 }) const [selected, setSelected] = useState() const rows = useMemo( () => derive(overview.data?.tasks || [], overview.data?.workers || []), [overview.data], ) const current = rows.find((r) => r.id === selected) || rows[0] return (

Projects

Projects orchestra is holding work for, derived from the tasks it knows about. The coordinator does not serve its project registry.

{overview.isError && (

/v1/ui/overview failed: {String(overview.error)}

)} {!overview.data && !overview.isError && (

reading /v1/ui/overview

)} {overview.data && rows.length === 0 && (

Every project here comes from a task. Orchestra holds{' '} {overview.data.tasks.length} tasks, so nothing groups yet.

)} {current && (
{rows.map((row) => ( setSelected(row.id)} style={{ cursor: 'pointer', background: row.id === current.id ? 'var(--bg-2)' : undefined, boxShadow: row.id === current.id ? 'inset 3px 0 0 0 var(--accent)' : undefined, }} > ))}
Project Tasks Leased Attention Last session evidence Workers
{row.id} {row.tasks.length} {row.active} {row.attention ? ( {row.attention} ) : ( 0 )} {fmt(row.lastSeen) || '—'} {row.workers.length === 0 ? ( none ) : ( row.workers.map((w) => ( {w.id} )) )}
Tasks →} >
{stateChips(current.counts)}
Workers declaring this project

{current.workers.length === 0 ? 'No registered worker accepts it. Orchestra does not treat an omitted declaration as a wildcard, so nothing can lease this work.' : 'Worker-declared, from the federation registry — not the coordinator’s project affinity.'}

{current.workers.map((w) => (
{w.id}{' '} {w.address || ''}
))}
{current.tasks.slice(0, 8).map((task) => ( {task.title || task.external_id || 'untitled'} {task.id} {task.state.replace('_', ' ')} ))}
{current.tasks.length > 8 && (
All {current.tasks.length} tasks →
)}
)}
) }