/** * Self-contained AgentHub task board, served at `GET /board`. * * Constraints: * - One static HTML page: inline CSS + JS, no framework and no runtime deps. * - Reads only same-origin endpoints (`/tasks`, `/handoffs`, `/decisions`, * `/tasks/:id/activity`, `/events` and `/status`). * - Never mutates state. */ export interface BoardColumn { key: string; label: string; } /** Task statuses, in board order. Mirrors `TaskStatus` in core/schema.ts. */ export const BOARD_COLUMNS: BoardColumn[] = [ { key: 'open', label: 'Open' }, { key: 'in_progress', label: 'In Progress' }, { key: 'review', label: 'Review' }, { key: 'done', label: 'Done' }, { key: 'cancelled', label: 'Cancelled' }, ]; function columnSkeleton(): string { return BOARD_COLUMNS.map( (c) => `
${c.label} 0
`, ).join('\n'); } function escapeHtml(value: string): string { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } export function renderBoardHtml(projectName = 'AgentHub Project'): string { const initialProjectName = JSON.stringify(projectName).replace(/ AgentHub Board
AgentHub ${projectNameHtml}
connecting
${columnSkeleton()}

Handoffs

none

Decisions

none
`; }