diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-04-04 09:39:03 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-04-04 09:39:03 +0000 |
| commit | 40513ffddba01467193c3c3e19468c7090f06215 (patch) | |
| tree | 58b83b4b4b28ea38ab7076d7b4f8cb4484e7cb9b /web | |
| parent | 70e90275c0a08649c314cae5280521bcd29272e6 (diff) | |
feat: Ship button on SHIPPABLE stories; checker report on READY task cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web')
| -rw-r--r-- | web/app.js | 41 | ||||
| -rw-r--r-- | web/style.css | 15 |
2 files changed, 56 insertions, 0 deletions
@@ -181,6 +181,20 @@ function createTaskCard(task) { card.appendChild(errEl); } + // Checker report for READY tasks where the checker flagged a problem. + if (task.state === 'READY' && task.checker_report) { + const reportEl = document.createElement('div'); + reportEl.className = 'task-checker-report'; + const label = document.createElement('span'); + label.className = 'task-checker-report-label'; + label.textContent = '⚠ Checker flagged:'; + const text = document.createElement('span'); + text.textContent = task.checker_report; + reportEl.appendChild(label); + reportEl.appendChild(text); + card.appendChild(reportEl); + } + // Changestats badge for COMPLETED/READY tasks const CHANGESTATS_STATES = new Set(['COMPLETED', 'READY']); if (CHANGESTATS_STATES.has(task.state) && task.changestats != null) { @@ -571,6 +585,33 @@ export function renderStoryCard(story, doc = document) { card.appendChild(meta); + // Ship button for SHIPPABLE stories. + if (story.status === 'SHIPPABLE') { + const shipBtn = doc.createElement('button'); + shipBtn.className = 'btn-primary story-ship-btn'; + shipBtn.textContent = 'Ship'; + shipBtn.addEventListener('click', async (e) => { + e.stopPropagation(); + shipBtn.disabled = true; + shipBtn.textContent = 'Shipping…'; + try { + const res = await fetch(`${API_BASE}/api/stories/${story.id}/ship`, { method: 'POST' }); + if (!res.ok) { + const body = await res.json().catch(() => ({})); + alert(body.error || `Ship failed (${res.status})`); + shipBtn.disabled = false; + shipBtn.textContent = 'Ship'; + } else { + renderStoriesPanel(); + } + } catch { + shipBtn.disabled = false; + shipBtn.textContent = 'Ship'; + } + }); + card.appendChild(shipBtn); + } + return card; } diff --git a/web/style.css b/web/style.css index 5766b6f..d3b01d0 100644 --- a/web/style.css +++ b/web/style.css @@ -1220,6 +1220,21 @@ dialog label select:focus { margin-bottom: 0.75rem; } +.task-checker-report { + margin: 0.5rem 0; + padding: 0.5rem 0.75rem; + background: var(--warning-bg, rgba(255, 180, 0, 0.12)); + border-left: 3px solid var(--warning, #f0a500); + border-radius: 4px; + font-size: 0.8rem; + color: var(--text); +} + +.task-checker-report-label { + font-weight: 600; + margin-right: 0.4rem; +} + .running-history { margin-top: 1.5rem; overflow-x: auto; |
