// Kanban board, cards, and the right-side detail panel // ISO timestamp -> "just now" / "5m ago" / "3h ago" / "2d ago" function relTime(iso) { if (!iso) return ''; const t = new Date(iso).getTime(); if (isNaN(t)) return iso; const s = Math.max(0, Math.floor((Date.now() - t) / 1000)); if (s < 10) return 'just now'; 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`; return `${Math.floor(h / 24)}d ago`; } function TaskCard({ task, project, agent, selected, onClick, animState, actions }) { const showApprove = task.column === 'review'; const showRollback = task.column === 'done'; const showRetry = task.column === 'failed'; const act = actions || {}; project = project || { color: 'var(--c-text-3)', name: task.project }; const typeLabel = { 'module-install': 'Module Install', 'update': 'Update', 'agent-task': 'Agent Task', 'system-task': 'System Task', }[task.type]; return (
{task.id} {typeLabel}

{task.title}

{project.name} {relTime(task.updated)}
{task.agent} {task.running ? ( <>
{Math.round(task.progress)}% ) : task.column === 'failed' ? ( halted ) : task.column === 'done' ? ( ) : null}
db: {task.affectsDb ? 'yes' : 'no'} approval: {task.requiresApproval ? 'required' : 'no'} {task.version}
{task.column === 'failed' && task.error && (
{task.error}
)}
e.stopPropagation()}> {task.column === 'backlog' && act.transition && ( )} {task.column === 'ready' && act.transition && ( )} {task.column === 'running' && act.transition && ( )} {showRetry && act.transition && } {showApprove && act.approve && } {showRollback && }
); } function Column({ column, tasks, projectsById, agentsById, selectedId, onSelect, animMap, actions }) { return (
{column.name} {tasks.length}
{tasks.length === 0 ? (
no tasks
) : tasks.map(t => ( onSelect(t.id)} actions={actions} /> ))}
); } function Board({ tasks, onSelect, selectedId, animMap, actions }) { const { COLUMNS, PROJECTS, AGENTS } = window.ORC_DATA; const projectsById = Object.fromEntries((PROJECTS || []).map(p => [p.id, p])); const agentsById = Object.fromEntries((AGENTS || []).map(a => [a.id, a])); return (
{(COLUMNS || []).map(col => ( t.column === col.id)} projectsById={projectsById} agentsById={agentsById} selectedId={selectedId} onSelect={onSelect} animMap={animMap} actions={actions} /> ))}
); } /* ---------------- Detail panel ---------------- */ const SAMPLE_LOGS = [ { time: '00:14:01.221', level: 'info', msg: 'Acquired lock on tenant=helio.prod' }, { time: '00:14:01.498', level: 'info', msg: 'Resolved migration plan: 3 ops, ~14m est.' }, { time: '00:14:02.012', level: 'info', msg: 'Snapshotted users (12,438,221 rows) → s3://hl-bk/2026-05-04T00-14' }, { time: '00:14:02.880', level: 'ok', msg: 'Snapshot integrity verified (sha256 matches manifest)' }, { time: '00:14:08.402', level: 'info', msg: 'CREATE TABLE users_new PARTITION BY HASH (org_id) PARTITIONS 12' }, { time: '00:14:09.140', level: 'info', msg: 'Backfill batch 1/12 → 1,036,518 rows in 642ms' }, { time: '00:14:09.802', level: 'warn', msg: 'Slow rebuild on idx_users_email (est. 4m, ~3x baseline)' }, { time: '00:14:14.011', level: 'info', msg: 'Backfill batch 4/12 → 1,041,002 rows in 681ms' }, { time: '00:14:18.553', level: 'info', msg: 'Switching read traffic to users_new in shadow mode' }, { time: '00:14:18.640', level: 'ok', msg: 'Shadow read parity: 100.000% across 50k probe queries' }, ]; const SAMPLE_DIFF = [ { kind: 'hunk', n: '', mark: '', text: '@@ migrations/2026_04_28_partition_users.sql' }, { kind: 'ctx', n: '12', mark: ' ', text: '-- Phase 2: cutover to partitioned schema' }, { kind: 'del', n: '13', mark: '-', text: 'CREATE TABLE users (' }, { kind: 'del', n: '14', mark: '-', text: ' id UUID PRIMARY KEY,' }, { kind: 'del', n: '15', mark: '-', text: ' org_id UUID NOT NULL,' }, { kind: 'add', n: '13', mark: '+', text: 'CREATE TABLE users (' }, { kind: 'add', n: '14', mark: '+', text: ' id UUID NOT NULL,' }, { kind: 'add', n: '15', mark: '+', text: ' org_id UUID NOT NULL,' }, { kind: 'add', n: '16', mark: '+', text: ' PRIMARY KEY (org_id, id)' }, { kind: 'add', n: '17', mark: '+', text: ') PARTITION BY HASH (org_id) PARTITIONS 12;' }, { kind: 'ctx', n: '18', mark: ' ', text: '' }, { kind: 'ctx', n: '19', mark: ' ', text: 'CREATE INDEX idx_users_email ON users (email);' }, { kind: 'del', n: '20', mark: '-', text: 'CREATE INDEX idx_users_org ON users (org_id);' }, { kind: 'add', n: '20', mark: '+', text: '-- idx_users_org redundant after partition key' }, ]; const SAMPLE_STEPS = [ { state: 'done', title: 'Plan validated', meta: ['planner-agent', '0.4s'] }, { state: 'done', title: 'Snapshot created', meta: ['migrator-agent', '0.9s', '1.2 GB'] }, { state: 'done', title: 'Schema applied (shadow)', meta: ['migrator-agent', '5.3s'] }, { state: 'running', title: 'Backfilling 12 partitions', meta: ['migrator-agent', '7/12 shards · 4m 12s'] }, { state: 'pending', title: 'Cutover read traffic', meta: ['sentinel-agent', 'queued'] }, { state: 'pending', title: 'Cutover write traffic', meta: ['sentinel-agent', 'queued'] }, { state: 'pending', title: 'Drop legacy table', meta: ['migrator-agent', 'awaits approval'] }, ]; function Logs() { return (
{SAMPLE_LOGS.map((l, i) => (
{l.time} {l.level} {l.msg}
))}
); } function Diff() { return (
migrations/2026_04_28_partition_users.sql +6 −4
{SAMPLE_DIFF.map((line, i) => (
{line.kind === 'hunk' ? (
{line.text}
) : ( <>
{line.n}
{line.mark}
{line.text}
)}
))}
); } function Steps() { return (
{SAMPLE_STEPS.map((step, i) => (
{step.state === 'done' ? : step.state === 'failed' ? : step.state === 'running' ? : i + 1}
{step.title}
{step.meta.map((m, j) => {m})}
))}
); } // audit entries -> log lines function AuditLog({ audit }) { if (!audit || audit.length === 0) return
no audit entries yet
; const levelOf = (a) => a.action === 'reject' || a.to_status === 'failed' ? 'err' : a.action === 'approve' || a.to_status === 'done' ? 'ok' : a.to_status === 'review' ? 'warn' : 'info'; return (
{audit.map(a => (
{new Date(a.created_at).toLocaleTimeString()} {a.action} {a.actor} {a.from_status || a.to_status ? ` · ${a.from_status || '∅'} → ${a.to_status || '∅'}` : ''}
))}
); } function ResultDiff({ result }) { const diff = result?.diff; if (!diff) return
no diff — agent has not produced a change yet
; if (typeof diff === 'string') { return
{diff}
; } return
{JSON.stringify(diff, null, 2)}
; } function LiveLogs({ logs }) { const endRef = React.useRef(null); React.useEffect(() => { endRef.current?.scrollIntoView({ block: 'end' }); }, [logs]); if (!logs || logs.length === 0) return
no log output yet
; return (
{logs.map(l => (
{new Date(l.created_at).toLocaleTimeString()} {l.level} {l.msg}
))}
); } function DetailPanel({ task, onClose, actions, logs }) { const [tab, setTab] = React.useState('live'); const [detail, setDetail] = React.useState(null); const open = !!task; const act = actions || {}; // fetch real detail (audit + allowedNext) when task changes React.useEffect(() => { if (!task) { setDetail(null); return; } let live = true; window.ORC_API.getTask(task.id).then(d => { if (live) setDetail(d); }).catch(() => {}); return () => { live = false; }; }, [task && task.id, task && task.column]); if (!task) { return