summaryrefslogtreecommitdiff
path: root/web/app.js
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-14 00:38:07 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-14 00:38:07 +0000
commit98ccde12b08ad0b7f53e42de959a72d8382179e3 (patch)
tree46af05f401aa41b182d3bc948faa9a1d7466a3df /web/app.js
parent6ac15be438e3692cbc2ae2f36ab2d69468fc6372 (diff)
feat: show subtask rollup on BLOCKED tasks waiting for subtasks
When a task is BLOCKED due to spawned subtasks (no question), the card footer now fetches and renders a list of subtask names with their state emoji instead of showing the question/answer input UI. The Cancel button remains in both cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web/app.js')
-rw-r--r--web/app.js39
1 files changed, 38 insertions, 1 deletions
diff --git a/web/app.js b/web/app.js
index ad60f34..6bcdf57 100644
--- a/web/app.js
+++ b/web/app.js
@@ -163,7 +163,11 @@ function createTaskCard(task) {
footer.appendChild(acceptBtn);
footer.appendChild(rejectBtn);
} else if (task.state === 'BLOCKED') {
- renderQuestionFooter(task, footer);
+ if (task.question) {
+ renderQuestionFooter(task, footer);
+ } else {
+ renderSubtaskRollup(task.id, footer);
+ }
const cancelBtn = document.createElement('button');
cancelBtn.className = 'btn-cancel';
cancelBtn.textContent = 'Cancel';
@@ -728,6 +732,39 @@ function renderQuestionFooter(task, footer) {
}
}
+const STATE_EMOJI = {
+ PENDING: '⏳', QUEUED: '🕐', RUNNING: '⚡', COMPLETED: '✅',
+ FAILED: '❌', CANCELLED: '🚫', TIMED_OUT: '⏱', BUDGET_EXCEEDED: '💸',
+ READY: '👀', BLOCKED: '⏸',
+};
+
+async function renderSubtaskRollup(taskId, 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 subtasks = await res.json();
+ if (!subtasks || subtasks.length === 0) {
+ container.textContent = 'Waiting for subtasks…';
+ return;
+ }
+ const ul = document.createElement('ul');
+ ul.className = 'subtask-list';
+ for (const st of subtasks) {
+ const li = document.createElement('li');
+ li.className = `subtask-item subtask-${st.state.toLowerCase()}`;
+ li.textContent = `${STATE_EMOJI[st.state] || '•'} ${st.name}`;
+ ul.appendChild(li);
+ }
+ container.appendChild(ul);
+ } catch {
+ container.textContent = 'Could not load subtasks.';
+ }
+}
+
async function handleAnswer(taskId, answer, footer) {
const btns = footer.querySelectorAll('button, input');
btns.forEach(el => { el.disabled = true; });