From 7f6254cdafc6143f80ee9ca8e482c36aff2c197e Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Mon, 16 Mar 2026 00:56:16 +0000 Subject: feat: replace static subtask placeholder with task description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a BLOCKED/READY task has no subtasks yet, show the task description (truncated to ~120 chars at a word boundary) instead of the generic 'Waiting for subtasks…' text. Falls back to task.name if no description, and finally to the original generic text if neither is present. - Add truncateToWordBoundary(text, maxLen=120) helper - Update renderSubtaskRollup(task, footer) to use task object instead of taskId - Update both READY and BLOCKED call sites - Add web/test/subtask-placeholder.test.mjs with 11 tests Co-Authored-By: Claude Sonnet 4.6 --- web/app.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'web/app.js') diff --git a/web/app.js b/web/app.js index a2b0ea9..408ffce 100644 --- a/web/app.js +++ b/web/app.js @@ -96,6 +96,12 @@ export function renderChangestatsBadge(stats, doc = (typeof document !== 'undefi return span; } +function truncateToWordBoundary(text, maxLen = 120) { + if (!text || text.length <= maxLen) return text; + const cut = text.lastIndexOf(' ', maxLen); + return (cut > 0 ? text.slice(0, cut) : text.slice(0, maxLen)) + '…'; +} + function createTaskCard(task) { const card = document.createElement('div'); card.className = 'task-card'; @@ -175,7 +181,7 @@ function createTaskCard(task) { }); footer.appendChild(btn); } else if (task.state === 'READY') { - renderSubtaskRollup(task.id, footer); + renderSubtaskRollup(task, footer); const acceptBtn = document.createElement('button'); acceptBtn.className = 'btn-accept'; acceptBtn.textContent = 'Accept'; @@ -196,7 +202,7 @@ function createTaskCard(task) { if (task.question) { renderQuestionFooter(task, footer); } else { - renderSubtaskRollup(task.id, footer); + renderSubtaskRollup(task, footer); } const cancelBtn = document.createElement('button'); cancelBtn.className = 'btn-cancel'; @@ -867,17 +873,18 @@ const STATE_EMOJI = { READY: '👀', BLOCKED: '⏸', }; -async function renderSubtaskRollup(taskId, footer) { +async function renderSubtaskRollup(task, footer) { footer.addEventListener('click', (e) => e.stopPropagation()); const container = document.createElement('div'); container.className = 'subtask-rollup'; footer.prepend(container); try { - const res = await fetch(`${API_BASE}/api/tasks/${taskId}/subtasks`); + const res = await fetch(`${API_BASE}/api/tasks/${task.id}/subtasks`); const subtasks = await res.json(); if (!subtasks || subtasks.length === 0) { - container.textContent = 'Waiting for subtasks…'; + const blurb = task.description || task.name; + container.textContent = blurb ? truncateToWordBoundary(blurb) : 'Waiting for subtasks…'; return; } const ul = document.createElement('ul'); -- cgit v1.2.3