Delete endpoint + trash UI, shared header across all pages, task-log SSE + in-card console, reviewer separate from assignee (board+team), codex board redesign (3/4 kanban + company half-donuts + per-agent bars + square cards). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import { loadConfig } from '../core/config.js';
|
|
import { listTasks } from '../core/services/taskService.js';
|
|
import { agentAvatar, designTokensCss, escapeHtml, appHeader, appHeaderCss, taskModalHtml, appHeaderJs } from './ui-shared.js';
|
|
import type { IndexEntry } from '../core/index.js';
|
|
|
|
function compactDuration(ms: number): string {
|
|
const s = Math.max(0, Math.floor(ms / 1000));
|
|
if (s < 60) return `${s}s`;
|
|
const m = Math.floor(s / 60);
|
|
if (m < 60) return `${m}m`;
|
|
const h = Math.floor(m / 60);
|
|
if (h < 48) return `${h}h`;
|
|
return `${Math.floor(h / 24)}d`;
|
|
}
|
|
|
|
function totalTime(t: IndexEntry): string {
|
|
const start = Date.parse(t.createdAt);
|
|
const end = Date.parse(t.updatedAt);
|
|
if (Number.isNaN(start) || Number.isNaN(end)) return '';
|
|
return compactDuration(end - start);
|
|
}
|
|
|
|
export function renderArchiveHtml(cwd: string): string {
|
|
const config = loadConfig(cwd);
|
|
const done = listTasks(cwd)
|
|
.filter((t) => t.status === 'done')
|
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
|
|
const rows = done.length
|
|
? done
|
|
.map(
|
|
(t) => `<a class="task-row" href="/tasks/${encodeURIComponent(t.id)}">
|
|
<span class="id">${escapeHtml(t.id)}</span>
|
|
<span class="title">${escapeHtml(t.title)}</span>
|
|
<span class="meta">${escapeHtml(totalTime(t))}</span>
|
|
<span class="agent">${agentAvatar(t.assignedTo, { size: 24 })}</span>
|
|
</a>`,
|
|
)
|
|
.join('')
|
|
: '<div class="empty">No done tasks yet.</div>';
|
|
|
|
return `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="color-scheme" content="dark" />
|
|
<title>AgentHub Done Archive</title>
|
|
<style>
|
|
${designTokensCss()}
|
|
${appHeaderCss()}
|
|
body { padding: 96px 20px 32px; }
|
|
.id,.meta { color:var(--muted);font:12px/1.4 var(--font-mono); }
|
|
main { max-width:1040px;margin:0 auto; }
|
|
.head { display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:10px; }
|
|
h1 { font-size:18px;margin:0; }
|
|
.task-list { display:grid;gap:7px; }
|
|
.task-row { display:grid;grid-template-columns:88px minmax(0,1fr) 90px auto;gap:10px;align-items:center;color:inherit;text-decoration:none;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 12px; }
|
|
.task-row:hover { border-color:rgba(88,166,255,.5); }
|
|
.title { min-width:0;overflow-wrap:anywhere;font-weight:600; }
|
|
.agent { display:flex;justify-content:flex-end; }
|
|
.empty { color:var(--muted);background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:14px; }
|
|
@media (max-width:640px){ .task-row{grid-template-columns:1fr;gap:4px}.agent{justify-content:flex-start} }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
${appHeader(config.projectName, 'archive')}
|
|
<main>
|
|
<div class="head">
|
|
<h1>Done Archive</h1>
|
|
<span class="meta">${done.length} done tasks</span>
|
|
</div>
|
|
<div class="task-list">${rows}</div>
|
|
</main>
|
|
${taskModalHtml()}
|
|
${appHeaderJs()}
|
|
</body>
|
|
</html>`;
|
|
}
|