diff options
Diffstat (limited to 'web/app.js')
| -rw-r--r-- | web/app.js | 64 |
1 files changed, 41 insertions, 23 deletions
@@ -275,42 +275,49 @@ function truncateToWordBoundary(text, maxLen = 120) { return (cut > 0 ? text.slice(0, cut) : text.slice(0, maxLen)) + '…'; } -function createTaskCard(task) { - const card = document.createElement('div'); +export function createTaskCard(task, doc = document) { + const card = doc.createElement('div'); card.className = 'task-card'; card.dataset.taskId = task.id; // Header: name + state badge - const header = document.createElement('div'); + const header = doc.createElement('div'); header.className = 'task-card-header'; - const name = document.createElement('span'); + const name = doc.createElement('span'); name.className = 'task-name'; name.textContent = task.name; - const badge = document.createElement('span'); + const badge = doc.createElement('span'); badge.className = 'state-badge'; badge.dataset.state = task.state; badge.textContent = task.state.replace(/_/g, ' '); header.append(name, badge); + if (task.state === 'RUNNING') { + const elapsed = doc.createElement('span'); + elapsed.className = 'running-elapsed'; + elapsed.dataset.startedAt = task.updated_at ?? ''; + elapsed.textContent = formatElapsed(task.updated_at); + header.append(elapsed); + } card.appendChild(header); // Meta: priority + created_at - const meta = document.createElement('div'); + const meta = doc.createElement('div'); meta.className = 'task-meta'; if (task.priority) { - const prio = document.createElement('span'); + const prio = doc.createElement('span'); prio.textContent = task.priority; meta.appendChild(prio); } if (task.created_at) { - const when = document.createElement('span'); + const when = doc.createElement('span'); when.textContent = formatDate(task.created_at); meta.appendChild(when); } if (task.project) { - const proj = document.createElement('span'); + const proj = doc.createElement('span'); proj.className = 'task-project'; proj.textContent = task.project; meta.appendChild(proj); @@ -319,7 +326,7 @@ function createTaskCard(task) { // Description (truncated via CSS) if (task.description) { - const desc = document.createElement('div'); + const desc = doc.createElement('div'); desc.className = 'task-description'; desc.textContent = task.description; card.appendChild(desc); @@ -328,7 +335,7 @@ function createTaskCard(task) { // Error message for failed tasks const FAILED_STATES = new Set(['FAILED', 'BUDGET_EXCEEDED', 'TIMED_OUT']); if (FAILED_STATES.has(task.state) && task.error_msg) { - const errEl = document.createElement('div'); + const errEl = doc.createElement('div'); errEl.className = 'task-error-msg'; errEl.textContent = task.error_msg; errEl.title = task.error_msg; @@ -337,12 +344,12 @@ function createTaskCard(task) { // Checker report for READY tasks where the checker flagged a problem. if (task.state === 'READY' && task.checker_report) { - const reportEl = document.createElement('div'); + const reportEl = doc.createElement('div'); reportEl.className = 'task-checker-report'; - const label = document.createElement('span'); + const label = doc.createElement('span'); label.className = 'task-checker-report-label'; label.textContent = '⚠ Checker flagged:'; - const text = document.createElement('span'); + const text = doc.createElement('span'); text.textContent = task.checker_report; reportEl.appendChild(label); reportEl.appendChild(text); @@ -363,17 +370,28 @@ function createTaskCard(task) { if (depBadge) card.appendChild(depBadge); } + // Log-tail placeholder for PENDING/QUEUED tasks (no execution exists yet). + if (task.state === 'PENDING' || task.state === 'QUEUED') { + const logTail = doc.createElement('div'); + logTail.className = 'task-log-tail'; + const placeholder = doc.createElement('p'); + placeholder.className = 'task-log-placeholder'; + placeholder.textContent = 'Waiting to start\u2026'; + logTail.appendChild(placeholder); + card.appendChild(logTail); + } + // Footer: action buttons based on state // Interrupted states (CANCELLED, FAILED, BUDGET_EXCEEDED) show both Resume and Restart. // TIMED_OUT shows Resume only. Others show a single action. const RESUME_STATES = new Set(['TIMED_OUT', 'CANCELLED', 'FAILED', 'BUDGET_EXCEEDED']); const RESTART_STATES = new Set(['CANCELLED', 'FAILED', 'BUDGET_EXCEEDED']); if (task.state === 'PENDING' || task.state === 'RUNNING' || task.state === 'READY' || task.state === 'BLOCKED' || RESUME_STATES.has(task.state)) { - const footer = document.createElement('div'); + const footer = doc.createElement('div'); footer.className = 'task-card-footer'; if (task.state === 'PENDING') { - const btn = document.createElement('button'); + const btn = doc.createElement('button'); btn.className = 'btn-run'; btn.textContent = 'Run'; btn.addEventListener('click', (e) => { @@ -382,7 +400,7 @@ function createTaskCard(task) { }); footer.appendChild(btn); } else if (task.state === 'RUNNING') { - const btn = document.createElement('button'); + const btn = doc.createElement('button'); btn.className = 'btn-cancel'; btn.textContent = 'Cancel'; btn.addEventListener('click', (e) => { @@ -392,14 +410,14 @@ function createTaskCard(task) { footer.appendChild(btn); } else if (task.state === 'READY') { renderSubtaskRollup(task, footer); - const acceptBtn = document.createElement('button'); + const acceptBtn = doc.createElement('button'); acceptBtn.className = 'btn-accept'; acceptBtn.textContent = 'Accept'; acceptBtn.addEventListener('click', (e) => { e.stopPropagation(); handleAccept(task.id, acceptBtn, footer); }); - const rejectBtn = document.createElement('button'); + const rejectBtn = doc.createElement('button'); rejectBtn.className = 'btn-reject'; rejectBtn.textContent = 'Reject'; rejectBtn.addEventListener('click', (e) => { @@ -414,7 +432,7 @@ function createTaskCard(task) { } else { renderSubtaskRollup(task, footer); } - const cancelBtn = document.createElement('button'); + const cancelBtn = doc.createElement('button'); cancelBtn.className = 'btn-cancel'; cancelBtn.textContent = 'Cancel'; cancelBtn.addEventListener('click', (e) => { @@ -423,7 +441,7 @@ function createTaskCard(task) { }); footer.appendChild(cancelBtn); } else if (RESUME_STATES.has(task.state)) { - const resumeBtn = document.createElement('button'); + const resumeBtn = doc.createElement('button'); resumeBtn.className = 'btn-resume'; resumeBtn.textContent = 'Resume'; resumeBtn.addEventListener('click', (e) => { @@ -432,7 +450,7 @@ function createTaskCard(task) { }); footer.appendChild(resumeBtn); if (RESTART_STATES.has(task.state)) { - const restartBtn = document.createElement('button'); + const restartBtn = doc.createElement('button'); restartBtn.className = 'btn-restart'; restartBtn.textContent = 'Restart'; restartBtn.addEventListener('click', (e) => { @@ -447,7 +465,7 @@ function createTaskCard(task) { } if (!NON_DELETABLE_STATES.has(task.state)) { - const delBtn = document.createElement('button'); + const delBtn = doc.createElement('button'); delBtn.className = 'btn-delete-task'; delBtn.title = 'Delete task'; delBtn.textContent = '✕'; |
