From dac676e8284725c8ec6de08282fe08a9b519ccc8 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Thu, 26 Mar 2026 09:01:35 +0000 Subject: feat: show tasks and subtasks in story detail view Story detail modal now fetches /api/stories/{id}/tasks and renders top-level tasks as a numbered list with subtasks nested beneath, using the same state emoji as the blocked/task views. Co-Authored-By: Claude Sonnet 4.6 --- web/app.js | 39 +++++++++++++++++++++++++++++++++++++++ web/style.css | 29 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) (limited to 'web') diff --git a/web/app.js b/web/app.js index 882ddac..dfe6d4e 100644 --- a/web/app.js +++ b/web/app.js @@ -3167,6 +3167,45 @@ function openStoryDetail(story) { addRow('Branch', story.branch_name); addRow('Created', story.created_at ? new Date(story.created_at).toLocaleString() : '—'); + // Load tasks for this story. + const tasksSection = document.createElement('div'); + tasksSection.className = 'story-detail-tasks'; + tasksSection.innerHTML = '

Loading tasks…

'; + body.appendChild(tasksSection); + + fetch(`${API_BASE}/api/stories/${story.id}/tasks`) + .then(r => r.ok ? r.json() : []) + .then(async tasks => { + tasksSection.innerHTML = ''; + const topLevel = tasks.filter(t => !t.parent_task_id); + if (topLevel.length === 0) { + tasksSection.innerHTML = '

No tasks yet.

'; + return; + } + const ol = document.createElement('ol'); + ol.className = 'story-detail-task-list'; + for (const t of topLevel) { + const li = document.createElement('li'); + li.className = `story-detail-task story-detail-task-${t.state.toLowerCase()}`; + li.textContent = `${STATE_EMOJI[t.state] || '•'} ${t.name}`; + const subs = tasks.filter(s => s.parent_task_id === t.id); + if (subs.length > 0) { + const ul = document.createElement('ul'); + ul.className = 'story-detail-subtask-list'; + for (const s of subs) { + const sli = document.createElement('li'); + sli.className = `subtask-item subtask-${s.state.toLowerCase()}`; + sli.textContent = `${STATE_EMOJI[s.state] || '•'} ${s.name}`; + ul.appendChild(sli); + } + li.appendChild(ul); + } + ol.appendChild(li); + } + tasksSection.appendChild(ol); + }) + .catch(() => { tasksSection.innerHTML = '

Could not load tasks.

'; }); + modal.showModal(); } diff --git a/web/style.css b/web/style.css index 1aa6627..c7e82ae 100644 --- a/web/style.css +++ b/web/style.css @@ -1924,3 +1924,32 @@ dialog label select:focus { .story-detail-body { padding: 0.25rem 0; } + +.story-detail-tasks { + margin-top: 1rem; + padding-top: 0.75rem; + border-top: 1px solid var(--border); +} + +.story-detail-task-list { + margin: 0.5rem 0 0; + padding-left: 1.25rem; + list-style: decimal; +} + +.story-detail-task-list > li { + padding: 0.2rem 0; + font-size: 0.9rem; +} + +.story-detail-subtask-list { + margin: 0.25rem 0 0.25rem 0.5rem; + padding-left: 1rem; + list-style: none; +} + +.story-detail-subtask-list li { + font-size: 0.85rem; + opacity: 0.85; + padding: 0.1rem 0; +} -- cgit v1.2.3